From 3f88c3dbfa6b7c8284b6c89284094388355d3ebf Mon Sep 17 00:00:00 2001 From: Matt Young Date: Fri, 28 Jun 2024 21:34:27 -0500 Subject: [PATCH 1/7] Foundation work --- .../Invoice/InvoiceAllStudentsPay.php | 47 +++++++++++++++++++ app/Services/Invoice/InvoiceDataService.php | 18 +++++++ 2 files changed, 65 insertions(+) create mode 100644 app/Services/Invoice/InvoiceAllStudentsPay.php create mode 100644 app/Services/Invoice/InvoiceDataService.php diff --git a/app/Services/Invoice/InvoiceAllStudentsPay.php b/app/Services/Invoice/InvoiceAllStudentsPay.php new file mode 100644 index 0000000..14adc18 --- /dev/null +++ b/app/Services/Invoice/InvoiceAllStudentsPay.php @@ -0,0 +1,47 @@ + Date: Fri, 28 Jun 2024 22:07:34 -0500 Subject: [PATCH 2/7] Rename services --- app/Http/Controllers/ResultsPage.php | 14 ++++++------ .../Tabulation/DoublerDecisionController.php | 4 ++-- .../Tabulation/TabulationController.php | 8 +++---- app/Http/Controllers/TestController.php | 4 ++-- app/Listeners/RefreshAuditionCache.php | 15 ++++++------- app/Listeners/RefreshEntryCache.php | 14 ++++++------ app/Providers/AppServiceProvider.php | 20 ++++++++--------- ...onCacheService.php => AuditionService.php} | 2 +- app/Services/DoublerService.php | 10 ++++----- ...EntryCacheService.php => EntryService.php} | 13 +++++++++-- app/Services/ScoreService.php | 2 +- app/Services/TabulationService.php | 22 +++++++++---------- resources/views/test.blade.php | 6 ++--- 13 files changed, 70 insertions(+), 64 deletions(-) rename app/Services/{AuditionCacheService.php => AuditionService.php} (99%) rename app/Services/{EntryCacheService.php => EntryService.php} (89%) diff --git a/app/Http/Controllers/ResultsPage.php b/app/Http/Controllers/ResultsPage.php index c00f5e5..d06c7fd 100644 --- a/app/Http/Controllers/ResultsPage.php +++ b/app/Http/Controllers/ResultsPage.php @@ -4,21 +4,21 @@ namespace App\Http\Controllers; use App\Models\Entry; use App\Models\Seat; -use App\Services\AuditionCacheService; +use App\Services\AuditionService; use App\Services\SeatingService; use Illuminate\Http\Request; use Illuminate\Support\Facades\Cache; class ResultsPage extends Controller { - protected $auditionCacheService; + protected $auditionService; protected $seatingService; - public function __construct(AuditionCacheService $auditionCacheService, SeatingService $seatingService) + public function __construct(AuditionService $auditionService, SeatingService $seatingService) { - $this->auditionCacheService = $auditionCacheService; - $this->seatingService = $seatingService; + $this->auditionService = $auditionService; + $this->seatingService = $seatingService; } /** @@ -26,7 +26,7 @@ class ResultsPage extends Controller */ public function __invoke(Request $request) { - $publishedAuditions = $this->auditionCacheService->getPublishedAuditions(); + $publishedAuditions = $this->auditionService->getPublishedAuditions(); $resultsSeatList = Cache::rememberForever('resultsSeatList', function () use ($publishedAuditions) { $seatList = []; // Load the $seatList in the form of $seatlist[audition_id] is an array of seats for that audition @@ -51,7 +51,7 @@ class ResultsPage extends Controller return $seatList; }); - $publishedAdvancementAuditions = $this->auditionCacheService->getPublishedAdvancementAuditions(); + $publishedAdvancementAuditions = $this->auditionService->getPublishedAdvancementAuditions(); $resultsAdvancementList = Cache::rememberForever('resultsAdvancementList', function () use ($publishedAdvancementAuditions) { $qualifierList = []; foreach ($publishedAdvancementAuditions as $audition) { diff --git a/app/Http/Controllers/Tabulation/DoublerDecisionController.php b/app/Http/Controllers/Tabulation/DoublerDecisionController.php index 76d4308..7d95056 100644 --- a/app/Http/Controllers/Tabulation/DoublerDecisionController.php +++ b/app/Http/Controllers/Tabulation/DoublerDecisionController.php @@ -6,14 +6,14 @@ use App\Http\Controllers\Controller; use App\Models\Entry; use App\Models\EntryFlag; use App\Services\DoublerService; -use App\Services\EntryCacheService; +use App\Services\EntryService; class DoublerDecisionController extends Controller { protected $doublerService; protected $entryService; - public function __construct(DoublerService $doublerService, EntryCacheService $entryService) + public function __construct(DoublerService $doublerService, EntryService $entryService) { $this->doublerService = $doublerService; $this->entryService = $entryService; diff --git a/app/Http/Controllers/Tabulation/TabulationController.php b/app/Http/Controllers/Tabulation/TabulationController.php index dbeddc8..78b5e7f 100644 --- a/app/Http/Controllers/Tabulation/TabulationController.php +++ b/app/Http/Controllers/Tabulation/TabulationController.php @@ -5,7 +5,7 @@ namespace App\Http\Controllers\Tabulation; use App\Http\Controllers\Controller; use App\Models\Audition; use App\Models\Seat; -use App\Services\AuditionCacheService; +use App\Services\AuditionService; use App\Services\DoublerService; use App\Services\SeatingService; use App\Services\TabulationService; @@ -22,17 +22,17 @@ class TabulationController extends Controller protected $seatingService; - protected $auditionCacheService; + protected $auditionService; public function __construct(TabulationService $tabulationService, DoublerService $doublerService, SeatingService $seatingService, - AuditionCacheService $auditionCacheService) + AuditionService $auditionService) { $this->tabulationService = $tabulationService; $this->doublerService = $doublerService; $this->seatingService = $seatingService; - $this->auditionCacheService = $auditionCacheService; + $this->auditionService = $auditionService; } public function status() diff --git a/app/Http/Controllers/TestController.php b/app/Http/Controllers/TestController.php index 64095f4..c24fbfe 100644 --- a/app/Http/Controllers/TestController.php +++ b/app/Http/Controllers/TestController.php @@ -2,7 +2,7 @@ namespace App\Http\Controllers; -use App\Services\AuditionCacheService; +use App\Services\AuditionService; use App\Services\TabulationService; use Illuminate\Http\Request; use Illuminate\Support\Facades\Session; @@ -12,7 +12,7 @@ class TestController extends Controller protected $scoringGuideCacheService; protected $tabulationService; - public function __construct(AuditionCacheService $scoringGuideCacheService, TabulationService $tabulationService) + public function __construct(AuditionService $scoringGuideCacheService, TabulationService $tabulationService) { $this->scoringGuideCacheService = $scoringGuideCacheService; $this->tabulationService = $tabulationService; diff --git a/app/Listeners/RefreshAuditionCache.php b/app/Listeners/RefreshAuditionCache.php index 89bb79c..0728bad 100644 --- a/app/Listeners/RefreshAuditionCache.php +++ b/app/Listeners/RefreshAuditionCache.php @@ -3,19 +3,18 @@ namespace App\Listeners; use App\Events\AuditionChange; -use App\Services\AuditionCacheService; -use Illuminate\Contracts\Queue\ShouldQueue; -use Illuminate\Queue\InteractsWithQueue; +use App\Services\AuditionService; class RefreshAuditionCache { - protected $auditionCacheService; + protected $auditionService; + /** * Create the event listener. */ - public function __construct(AuditionCacheService $cacheService) + public function __construct(AuditionService $cacheService) { - $this->auditionCacheService = $cacheService; + $this->auditionService = $cacheService; } /** @@ -24,9 +23,9 @@ class RefreshAuditionCache public function handle(AuditionChange $event): void { if ($event->refreshCache) { - $this->auditionCacheService->refreshCache(); + $this->auditionService->refreshCache(); } else { - $this->auditionCacheService->clearCache(); + $this->auditionService->clearCache(); } } } diff --git a/app/Listeners/RefreshEntryCache.php b/app/Listeners/RefreshEntryCache.php index 6fa0ecc..cac64e6 100644 --- a/app/Listeners/RefreshEntryCache.php +++ b/app/Listeners/RefreshEntryCache.php @@ -4,20 +4,20 @@ namespace App\Listeners; use App\Events\AuditionChange; use App\Events\EntryChange; -use App\Services\AuditionCacheService; -use App\Services\EntryCacheService; +use App\Services\AuditionService; +use App\Services\EntryService; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Queue\InteractsWithQueue; class RefreshEntryCache { - protected $entryCacheService; + protected $entryService; /** * Create the event listener. */ - public function __construct(EntryCacheService $cacheService) + public function __construct(EntryService $cacheService) { - $this->entryCacheService = $cacheService; + $this->entryService = $cacheService; } /** @@ -26,9 +26,9 @@ class RefreshEntryCache public function handle(EntryChange $event): void { if ($event->auditionId) { - $this->entryCacheService->clearEntryCacheForAudition($event->auditionId); + $this->entryService->clearEntryCacheForAudition($event->auditionId); } else { - $this->entryCacheService->clearEntryCaches(); + $this->entryService->clearEntryCaches(); } } } diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 0c5d0d7..8d6a8d7 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -34,9 +34,9 @@ use App\Observers\SeatingLimitObserver; use App\Observers\StudentObserver; use App\Observers\SubscoreDefinitionObserver; use App\Observers\UserObserver; -use App\Services\AuditionCacheService; +use App\Services\AuditionService; use App\Services\DoublerService; -use App\Services\EntryCacheService; +use App\Services\EntryService; use App\Services\ScoreService; use App\Services\SeatingService; use App\Services\TabulationService; @@ -50,31 +50,31 @@ class AppServiceProvider extends ServiceProvider */ public function register(): void { - $this->app->singleton(AuditionCacheService::class, function () { - return new AuditionCacheService(); + $this->app->singleton(AuditionService::class, function () { + return new AuditionService(); }); $this->app->singleton(SeatingService::class, function ($app) { return new SeatingService($app->make(TabulationService::class)); }); - $this->app->singleton(EntryCacheService::class, function ($app) { - return new EntryCacheService($app->make(AuditionCacheService::class)); + $this->app->singleton(EntryService::class, function ($app) { + return new EntryService($app->make(AuditionService::class)); }); $this->app->singleton(ScoreService::class, function ($app) { - return new ScoreService($app->make(AuditionCacheService::class), $app->make(EntryCacheService::class)); + return new ScoreService($app->make(AuditionService::class), $app->make(EntryService::class)); }); $this->app->singleton(TabulationService::class, function ($app) { return new TabulationService( - $app->make(AuditionCacheService::class), + $app->make(AuditionService::class), $app->make(ScoreService::class), - $app->make(EntryCacheService::class)); + $app->make(EntryService::class)); }); $this->app->singleton(DoublerService::class, function ($app) { - return new DoublerService($app->make(AuditionCacheService::class), $app->make(TabulationService::class), $app->make(SeatingService::class)); + return new DoublerService($app->make(AuditionService::class), $app->make(TabulationService::class), $app->make(SeatingService::class)); }); } diff --git a/app/Services/AuditionCacheService.php b/app/Services/AuditionService.php similarity index 99% rename from app/Services/AuditionCacheService.php rename to app/Services/AuditionService.php index 46e5b32..c45e064 100644 --- a/app/Services/AuditionCacheService.php +++ b/app/Services/AuditionService.php @@ -9,7 +9,7 @@ use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Session; -class AuditionCacheService +class AuditionService { protected $cacheKey = 'auditions'; diff --git a/app/Services/DoublerService.php b/app/Services/DoublerService.php index 5a1f777..a7e3c55 100644 --- a/app/Services/DoublerService.php +++ b/app/Services/DoublerService.php @@ -11,7 +11,7 @@ class DoublerService { protected $doublersCacheKey = 'doublers'; - protected $auditionCacheService; + protected $auditionService; protected $tabulationService; @@ -20,9 +20,9 @@ class DoublerService /** * Create a new class instance. */ - public function __construct(AuditionCacheService $auditionCacheService, TabulationService $tabulationService, SeatingService $seatingService) + public function __construct(AuditionService $auditionService, TabulationService $tabulationService, SeatingService $seatingService) { - $this->auditionCacheService = $auditionCacheService; + $this->auditionService = $auditionService; $this->tabulationService = $tabulationService; $this->seatingService = $seatingService; } @@ -100,13 +100,13 @@ class DoublerService $info[$entry->id] = [ 'entryID' => $entry->id, 'auditionID' => $entry->audition_id, - 'auditionName' => $this->auditionCacheService->getAudition($entry->audition_id)->name, + 'auditionName' => $this->auditionService->getAudition($entry->audition_id)->name, 'rank' => $this->tabulationService->entryRank($entry), 'unscored' => $this->tabulationService->remainingEntriesForAudition($entry->audition_id), 'limits' => $this->seatingService->getLimitForAudition($entry->audition_id), 'status' => $status, ]; - $entry->audition = $this->auditionCacheService->getAudition($entry->audition_id); + $entry->audition = $this->auditionService->getAudition($entry->audition_id); } return $info; diff --git a/app/Services/EntryCacheService.php b/app/Services/EntryService.php similarity index 89% rename from app/Services/EntryCacheService.php rename to app/Services/EntryService.php index d353d74..f2071d6 100644 --- a/app/Services/EntryCacheService.php +++ b/app/Services/EntryService.php @@ -6,14 +6,14 @@ use App\Models\Entry; use Illuminate\Support\Collection; use Illuminate\Support\Facades\Cache; -class EntryCacheService +class EntryService { protected $auditionCache; /** * Create a new class instance. */ - public function __construct(AuditionCacheService $auditionCache) + public function __construct(AuditionService $auditionCache) { $this->auditionCache = $auditionCache; } @@ -89,4 +89,13 @@ class EntryCacheService $this->clearEntryCacheForAudition($audition->id); } } + + public function entryIsLate(Entry $entry): bool + { + if ($entry->hasFlag('wave_late_fee')) { + return false; + } + + return $entry->created_at > $entry->audition->entry_deadline; + } } diff --git a/app/Services/ScoreService.php b/app/Services/ScoreService.php index 128f5f4..c7db47c 100644 --- a/app/Services/ScoreService.php +++ b/app/Services/ScoreService.php @@ -20,7 +20,7 @@ class ScoreService /** * Create a new class instance. */ - public function __construct(AuditionCacheService $auditionCache, EntryCacheService $entryCache) + public function __construct(AuditionService $auditionCache, EntryService $entryCache) { $this->auditionCache = $auditionCache; $this->entryCache = $entryCache; diff --git a/app/Services/TabulationService.php b/app/Services/TabulationService.php index e8899f2..3957432 100644 --- a/app/Services/TabulationService.php +++ b/app/Services/TabulationService.php @@ -9,9 +9,9 @@ use Illuminate\Support\Facades\Session; class TabulationService { - protected AuditionCacheService $auditionCacheService; + protected AuditionService $auditionService; - protected EntryCacheService $entryCacheService; + protected EntryService $entryService; protected ScoreService $scoreService; @@ -19,13 +19,13 @@ class TabulationService * Create a new class instance. */ public function __construct( - AuditionCacheService $scoringGuideCacheService, + AuditionService $auditionService, ScoreService $scoreService, - EntryCacheService $entryCacheService) + EntryService $entryService) { - $this->auditionCacheService = $scoringGuideCacheService; + $this->auditionService = $auditionService; $this->scoreService = $scoreService; - $this->entryCacheService = $entryCacheService; + $this->entryService = $entryService; } /** @@ -51,8 +51,8 @@ class TabulationService return $cache[$auditionId]; } - $audition = $this->auditionCacheService->getAudition($auditionId); - $entries = $this->entryCacheService->getEntriesForAudition($auditionId, $mode); + $audition = $this->auditionService->getAudition($auditionId); + $entries = $this->entryService->getEntriesForAudition($auditionId, $mode); $this->scoreService->calculateScoresForAudition($auditionId); // TODO will need to pass a mode to the above function to only use subscores for hte appropriate mode foreach ($entries as $entry) { @@ -91,7 +91,7 @@ class TabulationService public function entryScoreSheetsAreValid(Entry $entry): bool { //TODO consider making this move the invalid score to another database for further investigation - $validJudges = $this->auditionCacheService->getAudition($entry->audition_id)->judges; + $validJudges = $this->auditionService->getAudition($entry->audition_id)->judges; foreach ($entry->scoreSheets as $sheet) { if (! $validJudges->contains($sheet->user_id)) { $invalidJudge = User::find($sheet->user_id); @@ -135,11 +135,11 @@ class TabulationService return Cache::remember('auditionsWithStatus', 30, function () use ($mode) { // Retrieve auditions from the cache and load entry IDs - $auditions = $this->auditionCacheService->getAuditions($mode); + $auditions = $this->auditionService->getAuditions($mode); // Iterate over the auditions and calculate the scored_entries_count foreach ($auditions as $audition) { $scored_entries_count = 0; - $entries_to_check = $this->entryCacheService->getEntriesForAudition($audition->id); + $entries_to_check = $this->entryService->getEntriesForAudition($audition->id); switch ($mode) { case 'seating': diff --git a/resources/views/test.blade.php b/resources/views/test.blade.php index 8b6c30e..861bdbf 100644 --- a/resources/views/test.blade.php +++ b/resources/views/test.blade.php @@ -10,8 +10,8 @@ use Illuminate\Support\Facades\Session; @endphp @inject('scoreservice','App\Services\ScoreService'); -@inject('auditionService','App\Services\AuditionCacheService'); -@inject('entryService','App\Services\EntryCacheService') +@inject('auditionService','App\Services\AuditionService'); +@inject('entryService','App\Services\EntryService') @inject('seatingService','App\Services\SeatingService') Test Page @@ -22,6 +22,4 @@ @endphp - - From 830b7362a4a97038b68b88daaa940b674815a39d Mon Sep 17 00:00:00 2001 From: Matt Young Date: Sat, 29 Jun 2024 00:45:48 -0500 Subject: [PATCH 3/7] Invoicing working with simple HTML invoice displaying --- app/Http/Controllers/DashboardController.php | 28 ++++- app/Http/Controllers/TestController.php | 15 ++- app/Models/School.php | 10 +- app/Providers/InvoiceDataServiceProvider.php | 38 +++++++ .../Invoice/InvoiceAllStudentsPay.php | 47 -------- app/Services/Invoice/InvoiceDataService.php | 14 +-- .../Invoice/InvoiceOneFeePerEntry.php | 93 ++++++++++++++++ .../Invoice/InvoiceOneFeePerStudent.php | 100 ++++++++++++++++++ bootstrap/providers.php | 1 + resources/views/dashboard/invoice.blade.php | 44 ++++++++ resources/views/test.blade.php | 4 +- routes/user.php | 1 + 12 files changed, 327 insertions(+), 68 deletions(-) create mode 100644 app/Providers/InvoiceDataServiceProvider.php delete mode 100644 app/Services/Invoice/InvoiceAllStudentsPay.php create mode 100644 app/Services/Invoice/InvoiceOneFeePerEntry.php create mode 100644 app/Services/Invoice/InvoiceOneFeePerStudent.php create mode 100644 resources/views/dashboard/invoice.blade.php diff --git a/app/Http/Controllers/DashboardController.php b/app/Http/Controllers/DashboardController.php index 5b2c14b..dc551f4 100644 --- a/app/Http/Controllers/DashboardController.php +++ b/app/Http/Controllers/DashboardController.php @@ -2,13 +2,20 @@ namespace App\Http\Controllers; -use Illuminate\Http\Request; +use App\Services\Invoice\InvoiceDataService; use Illuminate\Support\Facades\Auth; -use function dd; + use function redirect; class DashboardController extends Controller { + protected InvoiceDataService $invoiceService; + + public function __construct(InvoiceDataService $invoiceService) + { + $this->invoiceService = $invoiceService; + } + public function profile() { return view('dashboard.profile'); @@ -22,10 +29,23 @@ class DashboardController extends Controller public function my_school() { if (Auth::user()->school) { - return redirect('/schools/' . Auth::user()->school->id); + return redirect('/schools/'.Auth::user()->school->id); } $possibilities = Auth::user()->possibleSchools(); - if (count($possibilities) < 1) return view('schools.create'); + if (count($possibilities) < 1) { + return view('schools.create'); + } + return view('dashboard.select_school', ['possibilities' => $possibilities]); } + + public function my_invoice() + { + if (! Auth::user()->school_id) { + return redirect()->route('dashboard')->with('error', 'You do not have a school to get an invoice for'); + } + $invoiceData = $this->invoiceService->allData(Auth::user()->school_id); + $school = Auth::user()->school; + return view('dashboard.invoice', compact('school', 'invoiceData')); + } } diff --git a/app/Http/Controllers/TestController.php b/app/Http/Controllers/TestController.php index c24fbfe..99fc429 100644 --- a/app/Http/Controllers/TestController.php +++ b/app/Http/Controllers/TestController.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers; use App\Services\AuditionService; +use App\Services\Invoice\InvoiceDataService; use App\Services\TabulationService; use Illuminate\Http\Request; use Illuminate\Support\Facades\Session; @@ -11,16 +12,22 @@ class TestController extends Controller { protected $scoringGuideCacheService; protected $tabulationService; + protected $invoiceService; - public function __construct(AuditionService $scoringGuideCacheService, TabulationService $tabulationService) - { + public function __construct( + AuditionService $scoringGuideCacheService, + TabulationService $tabulationService, + InvoiceDataService $invoiceService + ) { $this->scoringGuideCacheService = $scoringGuideCacheService; $this->tabulationService = $tabulationService; + $this->invoiceService = $invoiceService; } public function flashTest(Request $request) { - $auditions = $this->tabulationService->getAuditionsWithStatus(); - return view('test', compact('auditions')); + $lines = $this->invoiceService->getLines(12); + $totalFees = $this->invoiceService->getGrandTotal(12); + return view('test', compact('lines','totalFees')); } } diff --git a/app/Models/School.php b/app/Models/School.php index af18e16..93c886e 100644 --- a/app/Models/School.php +++ b/app/Models/School.php @@ -17,25 +17,28 @@ class School extends Model { return $this->hasMany(User::class); } + public function users(): HasMany { return $this->hasMany(User::class); } + public function emailDomains(): HasMany { return $this->hasMany(SchoolEmailDomain::class); } - public function initialLetterImageURL($bg_color = '4f46e5', $text_color='fff'): string + public function initialLetterImageURL($bg_color = '4f46e5', $text_color = 'fff'): string { $img = "https://ui-avatars.com/api/?background=$bg_color&color=$text_color&name="; - $img .= substr($this->name,0,1); + $img .= substr($this->name, 0, 1); + return $img; } public function students(): HasMany { - return $this->hasMany(Student::class); + return $this->hasMany(Student::class)->orderBy('last_name')->orderBy('first_name'); } public function entries(): HasManyThrough @@ -48,5 +51,4 @@ class School extends Model 'id', 'id'); } - } diff --git a/app/Providers/InvoiceDataServiceProvider.php b/app/Providers/InvoiceDataServiceProvider.php new file mode 100644 index 0000000..6556ed4 --- /dev/null +++ b/app/Providers/InvoiceDataServiceProvider.php @@ -0,0 +1,38 @@ +app->singleton(InvoiceDataService::class, function ($app) { + // Default binding, can be overridden in booth method + return new InvoiceOneFeePerEntry($app->make(EntryService::class)); + }); + } + + /** + * Bootstrap services. + */ + public function boot(): void + { + $this->app->singleton(InvoiceDataService::class, function ($app) { + return match (auditionSetting('fee_structure')) { + 'oneFeePerEntry' => new InvoiceOneFeePerEntry($app->make(EntryService::class)), + 'oneFeePerStudent' => new InvoiceOneFeePerStudent($app->make(EntryService::class)), + default => throw new \Exception('Unknown Invoice Method'), + }; + }); + } +} diff --git a/app/Services/Invoice/InvoiceAllStudentsPay.php b/app/Services/Invoice/InvoiceAllStudentsPay.php deleted file mode 100644 index 14adc18..0000000 --- a/app/Services/Invoice/InvoiceAllStudentsPay.php +++ /dev/null @@ -1,47 +0,0 @@ -entryService = $entryService; + } + + public function allData($schoolId) + { + static $schoolInvoiceData = []; + + if (Arr::has($schoolInvoiceData, $schoolId)) { + return $schoolInvoiceData[$schoolId]; + } + $school = School::findOrFail($schoolId); + + $invoiceData['lines'] = []; + $invoiceData['linesTotal'] = 0; + $invoiceData['lateFeesTotal'] = 0; + /** @noinspection PhpArrayIndexImmediatelyRewrittenInspection */ + $invoiceData['grandTotal'] = 0; + + $entries = $school->entries()->with('audition')->orderBy('created_at', 'desc')->get()->groupBy('student_id'); + foreach ($school->students as $student) { + foreach ($entries[$student->id] as $entry) { + $entryFee = $entry->audition->entry_fee / 100; + $lateFee = $this->entryService->entryIsLate($entry) ? auditionSetting('late_fee') / 100 : 0; + + $invoiceData['lines'][] = [ + 'student_name' => $student->full_name(true), + 'audition' => $entry->audition->name, + 'entry_timestamp' => $entry->created_at, + 'entry_fee' => $entryFee, + 'late_fee' => $lateFee, + ]; + $invoiceData['linesTotal'] += $entryFee; + $invoiceData['lateFeesTotal'] += $lateFee; + } + } + // School Fee Total + if (! auditionSetting('school_fee')) { + $invoiceData['schoolFeeTotal'] = 0; + } else { + $invoiceData['schoolFeeTotal'] = auditionSetting('school_fee') / 100; + } + + $invoiceData['grandTotal'] = $invoiceData['linesTotal'] + $invoiceData['lateFeesTotal'] + $invoiceData['schoolFeeTotal']; + $schoolInvoiceData[$school->id] = $invoiceData; + + return $invoiceData; + } + + public function getLines($schoolId) + { + return $this->allData($schoolId)['lines']; + } + + public function getLinesTotal($schoolId) + { + return $this->allData($schoolId)['linesTotal']; + } + + public function getLateFeesTotal($schoolId) + { + return $this->allData($schoolId)['lateFeesTotal']; + } + + public function getSchoolFeeTotal($schoolId) + { + return $this->allData($schoolId)['schoolFeeTotal']; + + } + + public function getGrandTotal($schoolId) + { + return $this->allData($schoolId)['grandTotal']; + } +} diff --git a/app/Services/Invoice/InvoiceOneFeePerStudent.php b/app/Services/Invoice/InvoiceOneFeePerStudent.php new file mode 100644 index 0000000..3bf9f99 --- /dev/null +++ b/app/Services/Invoice/InvoiceOneFeePerStudent.php @@ -0,0 +1,100 @@ +entryService = $entryService; + } + + public function allData($schoolId) + { + static $schoolInvoiceData = []; + + if (Arr::has($schoolInvoiceData, $schoolId)) { + return $schoolInvoiceData[$schoolId]; + } + $school = School::findOrFail($schoolId); + + $invoiceData['lines'] = []; + $invoiceData['linesTotal'] = 0; + $invoiceData['lateFeesTotal'] = 0; + /** @noinspection PhpArrayIndexImmediatelyRewrittenInspection */ + $invoiceData['grandTotal'] = 0; + + $entries = $school->entries()->with('audition')->orderBy('created_at', 'desc')->get()->groupBy('student_id'); + foreach ($school->students as $student) { + $firstEntryForStudent = true; + foreach ($entries[$student->id] as $entry) { + if ($firstEntryForStudent) { + $entryFee = $entry->audition->entry_fee / 100; + $lateFee = $this->entryService->entryIsLate($entry) ? auditionSetting('late_fee') / 100 : 0; + } else { + $entryFee = 0; + $lateFee = 0; + } + + $invoiceData['lines'][] = [ + 'student_name' => $student->full_name(true), + 'audition' => $entry->audition->name, + 'entry_timestamp' => $entry->created_at, + 'entry_fee' => $entryFee, + 'late_fee' => $lateFee, + ]; + $invoiceData['linesTotal'] += $entryFee; + $invoiceData['lateFeesTotal'] += $lateFee; + $firstEntryForStudent = false; + } + } + // School Fee Total + if (! auditionSetting('school_fee')) { + $invoiceData['schoolFeeTotal'] = 0; + } else { + $invoiceData['schoolFeeTotal'] = auditionSetting('school_fee') / 100; + } + + $invoiceData['grandTotal'] = $invoiceData['linesTotal'] + $invoiceData['lateFeesTotal'] + $invoiceData['schoolFeeTotal']; + $schoolInvoiceData[$school->id] = $invoiceData; + + return $invoiceData; + } + + public function getLines($schoolId) + { + return $this->allData($schoolId)['lines']; + } + + public function getLinesTotal($schoolId) + { + return $this->allData($schoolId)['linesTotal']; + } + + public function getLateFeesTotal($schoolId) + { + return $this->allData($schoolId)['lateFeesTotal']; + } + + public function getSchoolFeeTotal($schoolId) + { + return $this->allData($schoolId)['schoolFeeTotal']; + + } + + public function getGrandTotal($schoolId) + { + return $this->allData($schoolId)['grandTotal']; + } +} diff --git a/bootstrap/providers.php b/bootstrap/providers.php index 0ad9c57..9edefb0 100644 --- a/bootstrap/providers.php +++ b/bootstrap/providers.php @@ -3,4 +3,5 @@ return [ App\Providers\AppServiceProvider::class, App\Providers\FortifyServiceProvider::class, + App\Providers\InvoiceDataServiceProvider::class, ]; diff --git a/resources/views/dashboard/invoice.blade.php b/resources/views/dashboard/invoice.blade.php new file mode 100644 index 0000000..a8afc96 --- /dev/null +++ b/resources/views/dashboard/invoice.blade.php @@ -0,0 +1,44 @@ +@props(['school', 'invoiceData']) + + + Invoice - {{ $school->name }} + @php( dump($invoiceData )) + + Invoice + + + Student Name + Audition + Entry Timestamp + Entry Fee + Late Fee + + + + @foreach($invoiceData['lines'] as $line) + + {{ $line['student_name'] }} + {{ $line['audition'] }} + {{ $line['entry_timestamp']->setTimezone('America/Chicago')->format('m/d/Y g:i:s A') }} + ${{ number_format($line['entry_fee'],2) }} + ${{ number_format($line['late_fee'],2) }} + + @endforeach + + + + Totals + ${{ number_format($invoiceData['linesTotal'],2) }} + ${{ number_format($invoiceData['lateFeesTotal'],2) }} + + + School Fee + ${{ number_format($invoiceData['schoolFeeTotal'],2) }} + + + Grand Total + ${{ number_format($invoiceData['grandTotal'],2) }} + + + + diff --git a/resources/views/test.blade.php b/resources/views/test.blade.php index 861bdbf..ddd5fa6 100644 --- a/resources/views/test.blade.php +++ b/resources/views/test.blade.php @@ -16,9 +16,9 @@ Test Page @php - $schools = School::all(); + dump($totalFees); + dump($lines); - } @endphp diff --git a/routes/user.php b/routes/user.php index 3974d1a..8be2957 100644 --- a/routes/user.php +++ b/routes/user.php @@ -12,6 +12,7 @@ Route::middleware(['auth', 'verified'])->group(function () { Route::get('/dashboard', [DashboardController::class, 'dashboard'])->name('dashboard'); Route::get('/profile', [DashboardController::class, 'profile']); Route::get('/my_school', [DashboardController::class, 'my_school']); + Route::get('/my_invoice', [DashboardController::class, 'my_invoice'])->name('my_invoice'); }); // Entry Related Routes From cedb53b1558cf3b0794f0077e23d67f5f9956b22 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Sat, 29 Jun 2024 03:46:56 -0500 Subject: [PATCH 4/7] tweak invoice --- resources/views/dashboard/invoice.blade.php | 80 +++++++++++---------- 1 file changed, 43 insertions(+), 37 deletions(-) diff --git a/resources/views/dashboard/invoice.blade.php b/resources/views/dashboard/invoice.blade.php index a8afc96..c29d66a 100644 --- a/resources/views/dashboard/invoice.blade.php +++ b/resources/views/dashboard/invoice.blade.php @@ -2,43 +2,49 @@ Invoice - {{ $school->name }} - @php( dump($invoiceData )) - - Invoice - - - Student Name - Audition - Entry Timestamp - Entry Fee - Late Fee - - - - @foreach($invoiceData['lines'] as $line) +
+ + + + Student Name + Audition + Entry Timestamp + Entry Fee + Late Fee + + + + @foreach($invoiceData['lines'] as $line) + + {{ $line['student_name'] }} + {{ $line['audition'] }} + {{ $line['entry_timestamp']->setTimezone('America/Chicago')->format('m/d/Y g:i:s A') }} + ${{ number_format($line['entry_fee'],2) }} + ${{ number_format($line['late_fee'],2) }} + + @endforeach + + - {{ $line['student_name'] }} - {{ $line['audition'] }} - {{ $line['entry_timestamp']->setTimezone('America/Chicago')->format('m/d/Y g:i:s A') }} - ${{ number_format($line['entry_fee'],2) }} - ${{ number_format($line['late_fee'],2) }} + + Totals + ${{ number_format($invoiceData['linesTotal'],2) }} + ${{ number_format($invoiceData['lateFeesTotal'],2) }} - @endforeach - - - - Totals - ${{ number_format($invoiceData['linesTotal'],2) }} - ${{ number_format($invoiceData['lateFeesTotal'],2) }} - - - School Fee - ${{ number_format($invoiceData['schoolFeeTotal'],2) }} - - - Grand Total - ${{ number_format($invoiceData['grandTotal'],2) }} - - - + + + Total Entry Fees + ${{ number_format($invoiceData['linesTotal'] + $invoiceData['lateFeesTotal'], 2) }} + + + School Fee + ${{ number_format($invoiceData['schoolFeeTotal'],2) }} + + + Grand Total + ${{ number_format($invoiceData['grandTotal'],2) }} + + + +
From 9dd2c2d823b8e11a700b8b5308f0a39b44928720 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Sat, 29 Jun 2024 09:33:47 -0500 Subject: [PATCH 5/7] allow setting of fee structure on audition settings page --- app/Http/Controllers/Admin/AuditionSettings.php | 10 ++++++++-- resources/views/admin/audition-settings.blade.php | 15 +++++++++++---- resources/views/dashboard/invoice.blade.php | 8 ++++---- 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/app/Http/Controllers/Admin/AuditionSettings.php b/app/Http/Controllers/Admin/AuditionSettings.php index 8481279..6083f20 100644 --- a/app/Http/Controllers/Admin/AuditionSettings.php +++ b/app/Http/Controllers/Admin/AuditionSettings.php @@ -21,9 +21,15 @@ class AuditionSettings extends Controller 'organizerName' => ['required'], 'organizerEmail' => ['required', 'email'], 'registrationCode' => ['required'], - 'late_fee' => ['nullable', 'numeric'], - 'school_fee' => ['nullable', 'numeric'], + 'fee_structure' => ['required', 'in:oneFeePerEntry,oneFeePerStudent'], // Options should align with the boot method of InvoiceDataServiceProvider + 'late_fee' => ['nullable', 'numeric', 'min:0'], + 'school_fee' => ['nullable', 'numeric', 'min:0'], ]); + + // Store currency values as cents + $validData['late_fee'] = $validData['late_fee'] * 100; + $validData['school_fee'] = $validData['school_fee'] * 100; + // TODO implement olympic scoring foreach ($validData as $key => $value) { Settings::set($key, $value); diff --git a/resources/views/admin/audition-settings.blade.php b/resources/views/admin/audition-settings.blade.php index 8dc2b2a..eb338cf 100644 --- a/resources/views/admin/audition-settings.blade.php +++ b/resources/views/admin/audition-settings.blade.php @@ -42,12 +42,19 @@ Fee Structure - - + {{-- Values should be one of the options in the boot method InvoiceDataServiceProvider --}} + + - - + + diff --git a/resources/views/dashboard/invoice.blade.php b/resources/views/dashboard/invoice.blade.php index c29d66a..fa7e453 100644 --- a/resources/views/dashboard/invoice.blade.php +++ b/resources/views/dashboard/invoice.blade.php @@ -2,9 +2,9 @@ Invoice - {{ $school->name }} -
- - +
+ + Student Name Audition @@ -24,7 +24,7 @@ @endforeach - + Totals From b57187663c3d024bcb21d2a6fc7bb011b22cbf84 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Sat, 29 Jun 2024 10:02:20 -0500 Subject: [PATCH 6/7] Include links on Dashboard including invoice --- .../views/admin/audition-settings.blade.php | 10 ++-- .../views/components/card/list/row.blade.php | 2 +- resources/views/dashboard/dashboard.blade.php | 27 +++++++++ resources/views/dashboard/profile.blade.php | 56 ------------------- routes/user.php | 4 +- 5 files changed, 36 insertions(+), 63 deletions(-) diff --git a/resources/views/admin/audition-settings.blade.php b/resources/views/admin/audition-settings.blade.php index eb338cf..c32ad8a 100644 --- a/resources/views/admin/audition-settings.blade.php +++ b/resources/views/admin/audition-settings.blade.php @@ -24,7 +24,6 @@ Scoring Settings - If students cannot advance to further honor groups, leave next event name blank
Enable score entry by judges @@ -37,14 +36,17 @@ Financial Settings - If students cannot advance to further honor groups, leave next event name blank Fee Structure {{-- Values should be one of the options in the boot method InvoiceDataServiceProvider --}} - - + + 'a']) {{-- Use if the link to the right needs to be a button --}} -
  • merge(['class'=>'flex items-center justify-between gap-x-6 px-4 py-5 sm:px-6']) }}> +
  • merge(['class'=>'flex items-center justify-between gap-x-6 px-4 py-4 sm:px-6']) }}>
    {{ $slot }}
    diff --git a/resources/views/dashboard/dashboard.blade.php b/resources/views/dashboard/dashboard.blade.php index f3fbb6f..237d9f5 100644 --- a/resources/views/dashboard/dashboard.blade.php +++ b/resources/views/dashboard/dashboard.blade.php @@ -4,4 +4,31 @@ @if(! Auth::user()->school_id) You aren't currently associated with a school. Click here to choose or create one. @endif +
    +
    {{-- Column 1 --}} + + User Options + + + + My Profile + + + + + My School + + + @if(Auth::user()->school_id) + + + My Invoice + + + @endif + + +
    + +
    diff --git a/resources/views/dashboard/profile.blade.php b/resources/views/dashboard/profile.blade.php index 0512bea..3d6b6d2 100644 --- a/resources/views/dashboard/profile.blade.php +++ b/resources/views/dashboard/profile.blade.php @@ -18,59 +18,3 @@ - - - -{{--@php use Illuminate\Support\Facades\Auth; @endphp - - User Profile -
    - - @if (session('status') === 'profile-information-updated') -
    - Profile Info has been updated. -
    - @endif - -
    - @csrf - @method('PUT') -
    -
    - - - - - -
    -
    -
    - Cancel - Update Profile -
    -
    - -
    - - - @if (session('status') === 'password-updated') -
    - Password has been updated. -
    - @endif - - - - - - - -
    -
    -
    --}} diff --git a/routes/user.php b/routes/user.php index 8be2957..c00e9b0 100644 --- a/routes/user.php +++ b/routes/user.php @@ -10,8 +10,8 @@ use Illuminate\Support\Facades\Route; Route::middleware(['auth', 'verified'])->group(function () { Route::get('/dashboard', [DashboardController::class, 'dashboard'])->name('dashboard'); - Route::get('/profile', [DashboardController::class, 'profile']); - Route::get('/my_school', [DashboardController::class, 'my_school']); + Route::get('/profile', [DashboardController::class, 'profile'])->name('my_profile'); + Route::get('/my_school', [DashboardController::class, 'my_school'])->name('my_school'); Route::get('/my_invoice', [DashboardController::class, 'my_invoice'])->name('my_invoice'); }); From c3b48bf89cdc1df7ad91fe081cad55a80cf0373e Mon Sep 17 00:00:00 2001 From: Matt Young Date: Sat, 29 Jun 2024 10:22:25 -0500 Subject: [PATCH 7/7] Allow admin to access any schools invoice from the schools admin page --- .../Controllers/Admin/SchoolController.php | 21 ++++++++++++++++++- .../Invoice/InvoiceOneFeePerEntry.php | 2 +- .../Invoice/InvoiceOneFeePerStudent.php | 2 +- resources/views/admin/schools/index.blade.php | 8 ++++++- routes/admin.php | 1 + 5 files changed, 30 insertions(+), 4 deletions(-) diff --git a/app/Http/Controllers/Admin/SchoolController.php b/app/Http/Controllers/Admin/SchoolController.php index d34e7d7..af0ea3c 100644 --- a/app/Http/Controllers/Admin/SchoolController.php +++ b/app/Http/Controllers/Admin/SchoolController.php @@ -5,6 +5,7 @@ namespace App\Http\Controllers\Admin; use App\Http\Controllers\Controller; use App\Models\School; use App\Models\SchoolEmailDomain; +use App\Services\Invoice\InvoiceDataService; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; @@ -14,14 +15,25 @@ use function request; class SchoolController extends Controller { + protected $invoiceService; + + public function __construct(InvoiceDataService $invoiceController) + { + $this->invoiceService = $invoiceController; + } + public function index() { if (! Auth::user()->is_admin) { abort(403); } $schools = School::with(['users', 'students', 'entries'])->orderBy('name')->get(); + $schoolTotalFees = []; + foreach ($schools as $school) { + $schoolTotalFees[$school->id] = $this->invoiceService->getGrandTotal($school->id); + } - return view('admin.schools.index', ['schools' => $schools]); + return view('admin.schools.index', compact('schools', 'schoolTotalFees')); } public function show(Request $request, School $school) @@ -122,4 +134,11 @@ class SchoolController extends Controller // return a redirect to the previous URL return redirect()->back(); } + + public function viewInvoice(Request $request, School $school) + { + $invoiceData = $this->invoiceService->allData($school->id); + + return view('dashboard.invoice', compact('school', 'invoiceData')); + } } diff --git a/app/Services/Invoice/InvoiceOneFeePerEntry.php b/app/Services/Invoice/InvoiceOneFeePerEntry.php index 16e4665..b08b27d 100644 --- a/app/Services/Invoice/InvoiceOneFeePerEntry.php +++ b/app/Services/Invoice/InvoiceOneFeePerEntry.php @@ -37,7 +37,7 @@ class InvoiceOneFeePerEntry implements InvoiceDataService $entries = $school->entries()->with('audition')->orderBy('created_at', 'desc')->get()->groupBy('student_id'); foreach ($school->students as $student) { - foreach ($entries[$student->id] as $entry) { + foreach ($entries[$student->id] ?? [] as $entry) { $entryFee = $entry->audition->entry_fee / 100; $lateFee = $this->entryService->entryIsLate($entry) ? auditionSetting('late_fee') / 100 : 0; diff --git a/app/Services/Invoice/InvoiceOneFeePerStudent.php b/app/Services/Invoice/InvoiceOneFeePerStudent.php index 3bf9f99..c5090ac 100644 --- a/app/Services/Invoice/InvoiceOneFeePerStudent.php +++ b/app/Services/Invoice/InvoiceOneFeePerStudent.php @@ -38,7 +38,7 @@ class InvoiceOneFeePerStudent implements InvoiceDataService $entries = $school->entries()->with('audition')->orderBy('created_at', 'desc')->get()->groupBy('student_id'); foreach ($school->students as $student) { $firstEntryForStudent = true; - foreach ($entries[$student->id] as $entry) { + foreach ($entries[$student->id] ?? [] as $entry) { if ($firstEntryForStudent) { $entryFee = $entry->audition->entry_fee / 100; $lateFee = $this->entryService->entryIsLate($entry) ? auditionSetting('late_fee') / 100 : 0; diff --git a/resources/views/admin/schools/index.blade.php b/resources/views/admin/schools/index.blade.php index b640084..9ffc01e 100644 --- a/resources/views/admin/schools/index.blade.php +++ b/resources/views/admin/schools/index.blade.php @@ -4,7 +4,7 @@ Schools - Click school name to edit + Click school name to edit
    Click total fees for invoice
    New School @@ -12,6 +12,7 @@ Name + Total Fees Directors Students Entries @@ -22,6 +23,11 @@ @foreach($schools as $school) {{ $school->name }} + + + ${{ number_format($schoolTotalFees[$school->id],2) }} + + {{ $school->users->count() }} {{ $school->students->count() }} {{ $school->entries->count() }} diff --git a/routes/admin.php b/routes/admin.php index c318db7..4f7d721 100644 --- a/routes/admin.php +++ b/routes/admin.php @@ -99,6 +99,7 @@ Route::middleware(['auth', 'verified', CheckIfAdmin::class])->prefix('admin/')-> Route::get('/create', 'create')->name('admin.schools.create'); Route::get('/{school}', 'show')->name('admin.schools.show'); Route::get('/{school}/edit', 'edit')->name('admin.schools.edit'); + Route::get('/{school}/invoice', 'viewInvoice')->name('admin.schools.invoice'); Route::patch('/{school}', 'update')->name('admin.schools.update'); Route::post('/', 'store')->name('admin.schools.store'); Route::delete('/domain/{domain}', 'destroy_domain')->name('admin.schools.destroy_domain');