-
-
Notifications
You must be signed in to change notification settings - Fork 175
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #187 from range-of-motion/152-implement-budgeting
Implement budgeting
- Loading branch information
Showing
36 changed files
with
830 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
<?php | ||
|
||
namespace App\Http\Controllers; | ||
|
||
use App\Helper; | ||
use App\Repositories\BudgetRepository; | ||
use App\Repositories\TagRepository; | ||
use Illuminate\Http\Request; | ||
use Illuminate\Support\Facades\Auth; | ||
use Illuminate\Validation\ValidationException; | ||
|
||
class BudgetController extends Controller | ||
{ | ||
private $budgetRepository; | ||
private $tagRepository; | ||
|
||
public function __construct(BudgetRepository $budgetRepository, TagRepository $tagRepository) | ||
{ | ||
$this->budgetRepository = $budgetRepository; | ||
$this->tagRepository = $tagRepository; | ||
} | ||
|
||
public function index() | ||
{ | ||
return view('budgets.index', [ | ||
'budgets' => $this->budgetRepository->getActive() | ||
]); | ||
} | ||
|
||
public function create() | ||
{ | ||
return view('budgets.create', [ | ||
'tags' => session('space')->tags()->orderBy('created_at', 'DESC')->get() | ||
]); | ||
} | ||
|
||
public function store(Request $request) | ||
{ | ||
$request->validate($this->budgetRepository->getValidationRules()); | ||
|
||
$user = Auth::user(); | ||
$tag = $this->tagRepository->getById($request->tag_id); | ||
|
||
if (!$user->can('view', $tag)) { | ||
throw ValidationException::withMessages(['tag_id' => __('validation.forbidden')]); | ||
} | ||
|
||
if ($this->budgetRepository->doesExist(session('space')->id, $request->tag_id)) { | ||
return redirect('/budgets/create') | ||
->with('message', 'A budget like this already exists'); | ||
} | ||
|
||
$amount = Helper::rawNumberToInteger($request->amount); | ||
$this->budgetRepository->create(session('space')->id, $request->tag_id, $request->period, $amount); | ||
|
||
return redirect('/budgets'); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
<?php | ||
|
||
namespace App\Models; | ||
|
||
use App\Helper; | ||
use App\Repositories\BudgetRepository; | ||
use Illuminate\Database\Eloquent\Model; | ||
use Illuminate\Database\Eloquent\SoftDeletes; | ||
|
||
class Budget extends Model | ||
{ | ||
use SoftDeletes; | ||
|
||
protected $fillable = [ | ||
'space_id', | ||
'tag_id', | ||
'period', | ||
'amount', | ||
'starts_on' | ||
]; | ||
|
||
public function space() | ||
{ | ||
return $this->belongsTo(Space::class); | ||
} | ||
|
||
public function tag() | ||
{ | ||
return $this->belongsTo(Tag::class); | ||
} | ||
|
||
// Accessors | ||
public function getFormattedAmountAttribute() | ||
{ | ||
return Helper::formatNumber($this->amount / 100); | ||
} | ||
|
||
public function getSpentAttribute() | ||
{ | ||
return (new BudgetRepository())->getSpentById($this->id); | ||
} | ||
|
||
public function getFormattedSpentAttribute() | ||
{ | ||
return Helper::formatNumber($this->spent / 100); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
<?php | ||
|
||
namespace App\Repositories; | ||
|
||
use App\Models\Budget; | ||
use App\Models\Spending; | ||
use Exception; | ||
use Illuminate\Support\Facades\DB; | ||
|
||
class BudgetRepository | ||
{ | ||
public function getValidationRules() | ||
{ | ||
return [ | ||
'tag_id' => 'required|exists:tags,id', | ||
'period' => 'required|in:' . implode(',', $this->getSupportedPeriods()), | ||
'amount' => 'required|regex:/^\d*(\.\d{2})?$/' | ||
]; | ||
} | ||
|
||
public function getSupportedPeriods(): array | ||
{ | ||
return [ | ||
'yearly', | ||
'monthly', | ||
'weekly', | ||
'daily' | ||
]; | ||
} | ||
|
||
public function doesExist(int $spaceId, int $tagId): bool | ||
{ | ||
return DB::selectOne(' | ||
SELECT COUNT(*) AS count | ||
FROM budgets | ||
WHERE | ||
space_id = ? | ||
AND tag_id = ? | ||
AND ( | ||
starts_on <= NOW() | ||
AND ( | ||
ends_on IS NULL | ||
OR ends_on > NOW() | ||
) | ||
) | ||
', [ | ||
$spaceId, | ||
$tagId | ||
])->count > 0; | ||
} | ||
|
||
public function getActive() | ||
{ | ||
$today = date('Y-m-d'); | ||
|
||
return Budget::whereRaw('space_id = ?', [session('space')->id]) | ||
->whereRaw('starts_on <= ?', [$today]) | ||
->whereRaw('(ends_on >= ? OR ends_on IS NULL)', [$today]) | ||
->get(); | ||
} | ||
|
||
public function getById(int $id): ?Budget | ||
{ | ||
return Budget::find($id); | ||
} | ||
|
||
public function getSpentById(int $id): int | ||
{ | ||
$budget = $this->getById($id); | ||
|
||
if (!$budget) { | ||
throw new Exception('Could not find budget (where ID is ' . $id . ')'); | ||
} | ||
|
||
if ($budget->period === 'yearly') { | ||
return Spending::where('space_id', session('space')->id) | ||
->where('tag_id', $budget->tag->id) | ||
->whereRaw('YEAR(happened_on) = ?', [date('Y')]) | ||
->sum('amount'); | ||
} | ||
|
||
if ($budget->period === 'monthly') { | ||
return Spending::where('space_id', session('space')->id) | ||
->where('tag_id', $budget->tag->id) | ||
->whereRaw('MONTH(happened_on) = ?', [date('n')]) | ||
->whereRaw('YEAR(happened_on) = ?', [date('Y')]) | ||
->sum('amount'); | ||
} | ||
|
||
if ($budget->period === 'weekly') { | ||
return Spending::where('space_id', session('space')->id) | ||
->where('tag_id', $budget->tag->id) | ||
->whereRaw('WEEK(happened_on) = WEEK(NOW())') | ||
->sum('amount'); | ||
} | ||
|
||
if ($budget->period === 'daily') { | ||
return Spending::where('space_id', session('space')->id) | ||
->where('tag_id', $budget->tag->id) | ||
->whereRaw('happened_on = ?', [date('Y-m-d')]) | ||
->sum('amount'); | ||
} | ||
|
||
throw new Exception('No clue what to do with period "' . $budget->period . '"'); | ||
} | ||
|
||
public function create(int $spaceId, int $tagId, string $period, int $amount): ?Budget | ||
{ | ||
if ($this->doesExist($spaceId, $tagId)) { | ||
throw new Exception(vsprintf('Budget (with space ID being %s and tag ID being %s) already exists', [ | ||
$spaceId, | ||
$tagId | ||
])); | ||
} | ||
|
||
if (!in_array($period, $this->getSupportedPeriods())) { | ||
throw new Exception('Unknown period "' . $period . '"'); | ||
} | ||
|
||
return Budget::create([ | ||
'space_id' => $spaceId, | ||
'tag_id' => $tagId, | ||
'period' => $period, | ||
'amount' => $amount, | ||
'starts_on' => date('Y-m-d') | ||
]); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
28 changes: 28 additions & 0 deletions
28
database/migrations/2020_07_01_181201_create_budgets_table.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
<?php | ||
|
||
use Illuminate\Database\Migrations\Migration; | ||
use Illuminate\Database\Schema\Blueprint; | ||
use Illuminate\Support\Facades\Schema; | ||
|
||
class CreateBudgetsTable extends Migration | ||
{ | ||
public function up(): void | ||
{ | ||
Schema::create('budgets', function (Blueprint $table) { | ||
$table->id(); | ||
$table->unsignedInteger('space_id'); | ||
$table->unsignedInteger('tag_id'); | ||
$table->string('period'); | ||
$table->unsignedInteger('amount'); | ||
$table->date('starts_on'); | ||
$table->date('ends_on')->nullable(); | ||
$table->timestamps(); | ||
$table->softDeletes(); | ||
}); | ||
} | ||
|
||
public function down(): void | ||
{ | ||
Schema::dropIfExists('budgets'); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
<?php | ||
|
||
return [ | ||
'period' => 'Zeitraum' | ||
]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,5 +15,7 @@ | |
'description' => 'Beskrivelse', | ||
'amount' => 'Beløb', | ||
|
||
'period' => 'Periode', | ||
|
||
'file' => 'Fil' | ||
]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.