Merge pull request #8 from okorpheus/invoicing
Invoicing Feature Complete
This commit is contained in:
commit
247656f60b
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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'));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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'));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,20 +4,20 @@ 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->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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@
|
|||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Services\AuditionCacheService;
|
||||
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(AuditionCacheService $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'));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Services\EntryService;
|
||||
use App\Services\Invoice\InvoiceDataService;
|
||||
use App\Services\Invoice\InvoiceOneFeePerEntry;
|
||||
use App\Services\Invoice\InvoiceOneFeePerStudent;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use function auditionSetting;
|
||||
|
||||
class InvoiceDataServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register services.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
$this->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'),
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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';
|
||||
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services\Invoice;
|
||||
|
||||
interface InvoiceDataService
|
||||
{
|
||||
public function allData($schoolId);
|
||||
|
||||
public function getLines($schoolId);
|
||||
|
||||
public function getLinesTotal($schoolId);
|
||||
|
||||
public function getLateFeesTotal($schoolId);
|
||||
|
||||
public function getSchoolFeeTotal($schoolId);
|
||||
|
||||
public function getGrandTotal($schoolId);
|
||||
}
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services\Invoice;
|
||||
|
||||
use App\Models\School;
|
||||
use App\Services\EntryService;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
use function auditionSetting;
|
||||
|
||||
class InvoiceOneFeePerEntry implements InvoiceDataService
|
||||
{
|
||||
/**
|
||||
* Create a new class instance.
|
||||
*/
|
||||
protected $entryService;
|
||||
|
||||
public function __construct(EntryService $entryService)
|
||||
{
|
||||
$this->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'];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services\Invoice;
|
||||
|
||||
use App\Models\School;
|
||||
use App\Services\EntryService;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
use function auditionSetting;
|
||||
|
||||
class InvoiceOneFeePerStudent implements InvoiceDataService
|
||||
{
|
||||
/**
|
||||
* Create a new class instance.
|
||||
*/
|
||||
protected $entryService;
|
||||
|
||||
public function __construct(EntryService $entryService)
|
||||
{
|
||||
$this->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'];
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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':
|
||||
|
|
|
|||
|
|
@ -3,4 +3,5 @@
|
|||
return [
|
||||
App\Providers\AppServiceProvider::class,
|
||||
App\Providers\FortifyServiceProvider::class,
|
||||
App\Providers\InvoiceDataServiceProvider::class,
|
||||
];
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@
|
|||
|
||||
<x-layout.page-section>
|
||||
<x-slot:section_name>Scoring Settings</x-slot:section_name>
|
||||
<x-slot:section_description>If students cannot advance to further honor groups, leave next event name blank</x-slot:section_description>
|
||||
<x-form.body-grid columns="12" class="m-3">
|
||||
<div class="col-span-6 flex space-x-3">
|
||||
<x-form.toggle-checkbox name="judging_enabled"/><span>Enable score entry by judges</span>
|
||||
|
|
@ -37,17 +36,27 @@
|
|||
|
||||
<x-layout.page-section>
|
||||
<x-slot:section_name>Financial Settings</x-slot:section_name>
|
||||
<x-slot:section_description>If students cannot advance to further honor groups, leave next event name blank</x-slot:section_description>
|
||||
<x-form.body-grid columns="12" class="m-3">
|
||||
|
||||
<x-form.select name="fee_structure" colspan="6">
|
||||
<x-slot:label>Fee Structure</x-slot:label>
|
||||
<option>One fee per entry</option>
|
||||
<option>One fee per student</option>
|
||||
{{-- Values should be one of the options in the boot method InvoiceDataServiceProvider --}}
|
||||
<option value="oneFeePerEntry" {{ auditionSetting('fee_structure') === 'oneFeePerEntry' ? 'selected':'' }}>
|
||||
One fee per entry
|
||||
</option>
|
||||
<option value="oneFeePerStudent" {{ auditionSetting('fee_structure') === 'oneFeePerStudent' ? 'selected':'' }}>
|
||||
One fee per student - one late fee per student if any of their entries are late
|
||||
</option>
|
||||
</x-form.select>
|
||||
|
||||
<x-form.field label_text="Late Fee" name="late_fee" colspan="3" :value="auditionSetting('late_fee')"/>
|
||||
<x-form.field label_text="School Membership Fee" name="school_fee" colspan="3" :value="auditionSetting('school_fee')"/>
|
||||
<x-form.field label_text="Late Fee"
|
||||
name="late_fee"
|
||||
colspan="3"
|
||||
:value="number_format(auditionSetting('late_fee') / 100,2) "/>
|
||||
<x-form.field label_text="School Membership Fee"
|
||||
name="school_fee"
|
||||
colspan="3"
|
||||
:value="number_format(auditionSetting('school_fee') / 100,2)"/>
|
||||
|
||||
</x-form.body-grid>
|
||||
</x-layout.page-section>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
<x-card.card>
|
||||
<x-table.table with_title_area>
|
||||
<x-slot:title class="ml-3">Schools</x-slot:title>
|
||||
<x-slot:subtitle class="ml-3">Click school name to edit</x-slot:subtitle>
|
||||
<x-slot:subtitle class="ml-3">Click school name to edit<br>Click total fees for invoice</x-slot:subtitle>
|
||||
<x-slot:title_block_right class="mr-3">
|
||||
<x-form.button href="{{ route('admin.schools.create') }}">New School</x-form.button>
|
||||
</x-slot:title_block_right>
|
||||
|
|
@ -12,6 +12,7 @@
|
|||
<thead>
|
||||
<tr>
|
||||
<x-table.th>Name</x-table.th>
|
||||
<x-table.th>Total Fees</x-table.th>
|
||||
<x-table.th>Directors</x-table.th>
|
||||
<x-table.th>Students</x-table.th>
|
||||
<x-table.th>Entries</x-table.th>
|
||||
|
|
@ -22,6 +23,11 @@
|
|||
@foreach($schools as $school)
|
||||
<tr>
|
||||
<x-table.td><a href="/admin/schools/{{ $school->id }}">{{ $school->name }}</a></x-table.td>
|
||||
<x-table.td>
|
||||
<a href="{{ route('admin.schools.invoice',$school->id) }}">
|
||||
${{ number_format($schoolTotalFees[$school->id],2) }}
|
||||
</a>
|
||||
</x-table.td>
|
||||
<x-table.td>{{ $school->users->count() }}</x-table.td>
|
||||
<x-table.td>{{ $school->students->count() }}</x-table.td>
|
||||
<x-table.td>{{ $school->entries->count() }}</x-table.td>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
@props(['right_link_button_type' => 'a']) {{-- Use if the link to the right needs to be a button --}}
|
||||
<li {{ $attributes->merge(['class'=>'flex items-center justify-between gap-x-6 px-4 py-5 sm:px-6']) }}>
|
||||
<li {{ $attributes->merge(['class'=>'flex items-center justify-between gap-x-6 px-4 py-4 sm:px-6']) }}>
|
||||
<div class="flex min-w-0 gap-x-4">
|
||||
{{ $slot }}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -4,4 +4,31 @@
|
|||
@if(! Auth::user()->school_id)
|
||||
You aren't currently associated with a school. <a href="/my_school" class="text-blue-600">Click here to choose or create one.</a>
|
||||
@endif
|
||||
<div class="grid sm:grid-cols-2 md:grid-cols-4">
|
||||
<div>{{-- Column 1 --}}
|
||||
<x-card.card>
|
||||
<x-card.heading>User Options</x-card.heading>
|
||||
<x-card.list.body>
|
||||
<a href="{{ route('my_profile') }}">
|
||||
<x-card.list.row class="hover:bg-gray-200">
|
||||
My Profile
|
||||
</x-card.list.row>
|
||||
</a>
|
||||
<a href="{{ route('my_school') }}">
|
||||
<x-card.list.row class="hover:bg-gray-200">
|
||||
My School
|
||||
</x-card.list.row>
|
||||
</a>
|
||||
@if(Auth::user()->school_id)
|
||||
<a href="{{ route('my_invoice') }}">
|
||||
<x-card.list.row class="hover:bg-gray-200">
|
||||
My Invoice
|
||||
</x-card.list.row>
|
||||
</a>
|
||||
@endif
|
||||
</x-card.list.body>
|
||||
</x-card.card>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</x-layout.app>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
@props(['school', 'invoiceData'])
|
||||
|
||||
<x-layout.app>
|
||||
<x-slot:page_title>Invoice - {{ $school->name }}</x-slot:page_title>
|
||||
<div class="">
|
||||
<x-table.table class="">
|
||||
<thead class="">
|
||||
<tr>
|
||||
<x-table.th>Student Name</x-table.th>
|
||||
<x-table.th>Audition</x-table.th>
|
||||
<x-table.th>Entry Timestamp</x-table.th>
|
||||
<x-table.th>Entry Fee</x-table.th>
|
||||
<x-table.th>Late Fee</x-table.th>
|
||||
</tr>
|
||||
</thead>
|
||||
<x-table.body>
|
||||
@foreach($invoiceData['lines'] as $line)
|
||||
<tr>
|
||||
<x-table.td>{{ $line['student_name'] }}</x-table.td>
|
||||
<x-table.td>{{ $line['audition'] }}</x-table.td>
|
||||
<x-table.td>{{ $line['entry_timestamp']->setTimezone('America/Chicago')->format('m/d/Y g:i:s A') }}</x-table.td>
|
||||
<x-table.td>${{ number_format($line['entry_fee'],2) }}</x-table.td>
|
||||
<x-table.td>${{ number_format($line['late_fee'],2) }}</x-table.td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</x-table.body>
|
||||
<tfoot class="">
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<x-table.th class="text-right">Totals</x-table.th>
|
||||
<x-table.th>${{ number_format($invoiceData['linesTotal'],2) }}</x-table.th>
|
||||
<x-table.th>${{ number_format($invoiceData['lateFeesTotal'],2) }}</x-table.th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="3"></td>
|
||||
<x-table.th class="text-right">Total Entry Fees</x-table.th>
|
||||
<x-table.th>${{ number_format($invoiceData['linesTotal'] + $invoiceData['lateFeesTotal'], 2) }}</x-table.th>
|
||||
</tr>
|
||||
<tr>
|
||||
<x-table.th colspan="4" class="text-right">School Fee</x-table.th>
|
||||
<x-table.th >${{ number_format($invoiceData['schoolFeeTotal'],2) }}</x-table.th>
|
||||
</tr>
|
||||
<tr>
|
||||
<x-table.th colspan="4" class="text-right">Grand Total</x-table.th>
|
||||
<x-table.th >${{ number_format($invoiceData['grandTotal'],2) }}</x-table.th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</x-table.table>
|
||||
</div>
|
||||
</x-layout.app>
|
||||
|
|
@ -18,59 +18,3 @@
|
|||
</x-layout.page-section>
|
||||
</x-layout.page-section-container>
|
||||
</x-layout.app>
|
||||
|
||||
|
||||
|
||||
{{--@php use Illuminate\Support\Facades\Auth; @endphp
|
||||
<x-layout.app>
|
||||
<x-slot:page_title>User Profile</x-slot:page_title>
|
||||
<div class="space-y-10 divide-y divide-gray-900/10">
|
||||
<x-layout.page-section
|
||||
section_name="Personal Information"
|
||||
section_description="Use a permanent address where you receive mail"
|
||||
>
|
||||
@if (session('status') === 'profile-information-updated')
|
||||
<div class="mt-4 px-8 font-medium text-sm text-green-600">
|
||||
Profile Info has been updated.
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<form action="/user/profile-information" method="POST">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
<div class="px-4 py-6 sm:p-8">
|
||||
<div class="grid max-w-2xl grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">
|
||||
<x-form.field name="first_name" label="First Name" value="{{ Auth::user()->first_name }}" div_classes="sm:col-span-3" />
|
||||
<x-form.field name="last_name" label="Last Name" value="{{ Auth::user()->last_name }}" div_classes="sm:col-span-3" />
|
||||
<x-form.field name="email" label="Email Address" value="{{ Auth::user()->email }}" div_classes="sm:col-span-3" />
|
||||
<x-form.field name="cell_phone" label="Cell Phone" value="{{ Auth::user()->cell_phone }}" div_classes="sm:col-span-3" />
|
||||
<x-form.field name="judging_preference" label="Judging Preference" value="{{ Auth::user()->judging_preference }}" div_classes="sm:col-span-5" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-end gap-x-6 border-t border-gray-900/10 px-4 py-4 sm:px-8">
|
||||
<x-form.button-nocolor type="button">Cancel</x-form.button-nocolor>
|
||||
<x-form.button>Update Profile</x-form.button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</x-layout.page-section>
|
||||
|
||||
<x-layout.page-section
|
||||
section_name="Change Password"
|
||||
section_description="Update your user password"
|
||||
>
|
||||
@if (session('status') === 'password-updated')
|
||||
<div class="mt-4 px-8 font-medium text-sm text-green-600">
|
||||
Password has been updated.
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<x-form.card method="PUT" action="/user/password" cols="1" submit-button-text="Change Password">
|
||||
<x-form.field name="current_password" label="Current Password" type="password" autocomplete="current-password" required />
|
||||
<x-form.field name="password" label="New Password" type="password" autocomplete="new-password" required />
|
||||
<x-form.field name="password_confirmation" label="Confirm New Password" autocomplete="new-password" type="password" required />
|
||||
</x-form.card>
|
||||
|
||||
</x-layout.page-section>
|
||||
</div>
|
||||
</x-layout.app>--}}
|
||||
|
|
|
|||
|
|
@ -10,18 +10,16 @@
|
|||
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')
|
||||
<x-layout.app>
|
||||
<x-slot:page_title>Test Page</x-slot:page_title>
|
||||
@php
|
||||
$schools = School::all();
|
||||
dump($totalFees);
|
||||
dump($lines);
|
||||
|
||||
}
|
||||
@endphp
|
||||
|
||||
|
||||
|
||||
|
||||
</x-layout.app>
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -10,8 +10,9 @@ 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');
|
||||
});
|
||||
|
||||
// Entry Related Routes
|
||||
|
|
|
|||
Loading…
Reference in New Issue