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

Monthly email report #feature #4

Merged
merged 3 commits into from
Jan 30, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
157 changes: 157 additions & 0 deletions app/BusinessLogic/ReportManager.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
<?php

namespace App\BusinessLogic;

use App\Contracts\ReportManager as ReportManagerContract;
use App\Domain\Ranges\CurrentMonth;
use App\Domain\Ranges\LastMonth;
use App\Models\Brand;
use App\Models\Category;
use App\Models\Transaction;

class ReportManager implements ReportManagerContract
{
protected $data = [];

public function generate()
{
$newBrands = Brand::whereBetween('created_at', [now()->startOfMonth(), now()->endOfMonth()])->pluck('name');

$currentMonthRange = new CurrentMonth;
$lastMonthRange = new LastMonth;

$this->addSection('Overview', $this->getOverviewData());

foreach(Category::all() as $category)
{
$brandsData = [];

foreach($category->brands as $brand) {
$totalCurrentMonth = $brand->transactions()->whereBetween('created_at', [$currentMonthRange->start(), $currentMonthRange->end()])->sum('amount');
$totalLastMonth = $brand->transactions()->whereBetween('created_at', [$lastMonthRange->start(), $lastMonthRange->end()])->sum('amount');
$change = ! $totalLastMonth ? '-' : number_format(($totalCurrentMonth / $totalLastMonth - 1) * 100, 2);

$brandsData[] = [
'name' => $brand->name,
'total_current_month' => $totalCurrentMonth,
'total_previous_month' => $totalLastMonth,
'change' => $change,
'change_color' => $this->getChangeColor($change, $category->type),
'is_new' => $newBrands->contains($brand->name)
];
}

$brandsData = $this->calculateAndAddAllBrandsData($brandsData, $category);

$this->addSection($category->name, $brandsData);
}

return $this->data;
}

protected function addSection($sectionName, $data)
{
$this->data[$sectionName] = $data;
}

protected function getChangeColor($change, $type)
{
if($change == '-') {
return 'gray';
}

if($type == Category::INCOME) {
return $change >= 0 ? 'green' : 'red';
}

return $change >= 0 ? 'red' : 'green';
}

protected function calculateAndAddAllBrandsData($brandsData, $category)
{
$allCurrentMonth = array_reduce($brandsData, function ($carry, $item) {
$carry += $item['total_current_month'];

return $carry;
});

$allLastMonth = array_reduce($brandsData, function ($carry, $item) {
$carry += $item['total_previous_month'];

return $carry;
});

$change = ! $allLastMonth ? '-' : number_format(($allCurrentMonth / $allLastMonth - 1) * 100, 2);

return array_merge([[
'name' => 'All',
'total_current_month' => $allCurrentMonth,
'total_previous_month' => $allLastMonth,
'change' => $change,
'change_color' => $this->getChangeColor($change, $category->type)
]], $brandsData);
}

protected function getOverviewData()
{
return [
$this->getTotalCash(),
$this->getTotalIncome(),
$this->getTotalExpenses(),
];
}

protected function getTotalCash()
{
$totalIncome = Transaction::income()->sum('amount');
$totalExpenses = Transaction::expenses()->sum('amount');

$totalIncomeExcludingThisMonth = Transaction::income()->where('created_at', '<', now()->startOfMonth())->sum('amount');
$totalExpensesExcludingThisMonth = Transaction::expenses()->where('created_at', '<', now()->startOfMonth())->sum('amount');

$totalCashTillNow = $totalIncome - $totalExpenses;
$totalCashExcludingThisMonth = $totalIncomeExcludingThisMonth - $totalExpensesExcludingThisMonth;

$change = ! $totalCashExcludingThisMonth ? '-' : number_format(($totalCashTillNow / $totalCashExcludingThisMonth - 1) * 100, 2);

return [
'name' => 'Total Cash',
'total_current_month' => $totalCashTillNow,
'total_previous_month' => $totalCashExcludingThisMonth,
'change' => $change,
'change_color' => $this->getChangeColor($change, 'INCOME')
];
}

protected function getTotalIncome()
{
$total = Transaction::income()->whereBetween('created_at', [now()->startOfMonth(), now()->endOfMonth()])->sum('amount');
$totalExcludingThisMonth = Transaction::income()->whereBetween('created_at', [now()->subMonth()->startOfMonth(), now()->subMonth()->endOfMonth()])->sum('amount');

$change = ! $totalExcludingThisMonth ? '-' : number_format(($total / $totalExcludingThisMonth - 1) * 100, 2);

return [
'name' => 'Total Income',
'total_current_month' => $total,
'total_previous_month' => $totalExcludingThisMonth,
'change' => $change,
'change_color' => $this->getChangeColor($change, 'INCOME')
];
}

protected function getTotalExpenses()
{
$total = Transaction::expenses()->whereBetween('created_at', [now()->startOfMonth(), now()->endOfMonth()])->sum('amount');
$totalExcludingThisMonth = Transaction::expenses()->whereBetween('created_at', [now()->subMonth()->startOfMonth(), now()->subMonth()->endOfMonth()])->sum('amount');

$change = ! $totalExcludingThisMonth ? '-' : number_format(($total / $totalExcludingThisMonth - 1) * 100, 2);

return [
'name' => 'Total Expenses',
'total_current_month' => $total,
'total_previous_month' => $totalExcludingThisMonth,
'change' => $change,
'change_color' => $this->getChangeColor($change, 'EXPENSES')
];
}
}
46 changes: 46 additions & 0 deletions app/Console/Commands/ReportCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

namespace App\Console\Commands;

use App\Models\User;
use Illuminate\Console\Command;
use App\Notifications\FinanceMonthlyReportNotification;

class ReportCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'finance:report';

/**
* The console command description.
*
* @var string
*/
protected $description = 'Report the finance data for the current month';

/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}

/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
User::first()->notify(new FinanceMonthlyReportNotification);

return 0;
}
}
2 changes: 1 addition & 1 deletion app/Console/Kernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class Kernel extends ConsoleKernel
*/
protected function schedule(Schedule $schedule)
{
// $schedule->command('inspire')->hourly();
$schedule->command('finance:report')->onOneServer()->lastDayOfMonth('23:00');
}

/**
Expand Down
8 changes: 8 additions & 0 deletions app/Contracts/ReportManager.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

namespace App\Contracts;

interface ReportManager
{
public function generate();
}
69 changes: 69 additions & 0 deletions app/Notifications/FinanceMonthlyReportNotification.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

namespace App\Notifications;

use App\Contracts\ReportManager;
use App\Services\PdfRenderer;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;

class FinanceMonthlyReportNotification extends Notification
{
use Queueable;

/**
* Create a new notification instance.
*
* @return void
*/
public function __construct()
{
//
}

/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via($notifiable)
{
return ['mail'];
}

/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
* @return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
$data = [
'sections' => app(ReportManager::class)->generate(),
'currency' => config('finance.currency'),
'month' => now()->format('M Y')
];

return (new MailMessage)
->subject('Finance report for ' . $data['month'])
->line('Please find the finance report attachment for ' . $data['month'])
->attachData(PdfRenderer::render('report', $data), 'finance-report.pdf');
}

/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @return array
*/
public function toArray($notifiable)
{
return [
//
];
}
}
3 changes: 3 additions & 0 deletions app/Providers/AppServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
use App\BusinessLogic\SmsParser;
use Illuminate\Support\ServiceProvider;
use App\BusinessLogic\SmsTemplateDetector;
use App\BusinessLogic\ReportManager;
use App\BusinessLogic\SmsTransactionProcessor;
use App\Contracts\SmsParser as SmsParserContract;
use App\Contracts\SmsTemplateDetector as SmsTemplateDetectorContract;
use App\Contracts\ReportManager as ReportManagerContract;
use App\Contracts\SmsTransactionProcessor as SmsTransactionProcessorContract;
use App\Domain\Ranges\CurrentMonth;
use App\Domain\Ranges\CurrentYear;
Expand All @@ -27,6 +29,7 @@ public function register()
$this->app->bind(SmsParserContract::class, SmsParser::class);
$this->app->bind(SmsTemplateDetectorContract::class, SmsTemplateDetector::class);
$this->app->bind(SmsTransactionProcessorContract::class, SmsTransactionProcessor::class);
$this->app->bind(ReportManagerContract::class, ReportManager::class);

// TODO: find a better way to load classes based on base class Range
$this->app->bind('findRangeByKey', function($_, $params) {
Expand Down
28 changes: 28 additions & 0 deletions app/Services/PdfRenderer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

namespace App\Services;

use Dompdf\Dompdf;
use Dompdf\Options;
use Illuminate\Support\Facades\View;

class PdfRenderer
{
public static function render($view, $data)
{
if (! defined('DOMPDF_ENABLE_AUTOLOAD')) {
define('DOMPDF_ENABLE_AUTOLOAD', false);
}

$dompdfOptions = new Options();
$dompdfOptions->setChroot(base_path());

$dompdf = new Dompdf($dompdfOptions);
$dompdf->setPaper('A4');

$dompdf->loadHtml(View::make($view, $data)->render());
$dompdf->render();

return (string) $dompdf->output();
}
}
2 changes: 2 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
"license": "MIT",
"require": {
"php": "^7.3|^8.0",
"aws/aws-sdk-php": "^3.199",
"binarytorch/larecipe": "^2.4",
"dompdf/dompdf": "^1.1",
"fruitcake/laravel-cors": "^2.0",
"guzzlehttp/guzzle": "^7.0.1",
"inertiajs/inertia-laravel": "^0.4.5",
Expand Down