From 564a851d6dfa78763ca7c19994c5dcfdcd7d6227 Mon Sep 17 00:00:00 2001 From: Fenrikur <3359222+Fenrikur@users.noreply.github.com> Date: Wed, 17 May 2023 17:41:21 +0200 Subject: [PATCH] Improve user experience for table assignment and status notification (#3) * prevent unnecessary updates to status timestamps * notify admin user about sent status notifications * auto update status on table type change in list view * prevent calling update on accepted tables and delete in general * prevent shares/assistants from accepting a table * prevent cancellation once table has been accepted * prevent modification of shares after registration period * change styling of buttons on table confirmation * fix issue with automatic status change * update child applications to TableAccepted on parent accepting their offered table * update table number for assistants with parent * only send status notifications to Dealers * show status information on Dasboard * show reg status on dashboard * rework status flow during acceptance phase dropping is_notified add notifications for shares improve status messages in dasboard * allow sorting and searching applications by dealership * disable placeholder selection to avoid error in applications list --- app/Enums/ApplicationStatus.php | 1 + app/Enums/StatusNotificationResult.php | 13 ++ .../Resources/ApplicationResource.php | 135 +++++++++++++----- .../Pages/EditApplication.php | 55 ++++++- .../Resources/UserResource/Pages/EditUser.php | 2 +- .../Applications/ApplicationController.php | 79 +++++++--- .../Applications/InviteesController.php | 20 +-- app/Http/Controllers/DashboardController.php | 11 +- .../Controllers/TableVerifyController.php | 37 +++-- app/Models/Application.php | 129 ++++++++++++++--- app/Notifications/AcceptedNotification.php | 46 ------ ... => AlternateTableOfferedNotification.php} | 12 +- ...AlternateTableOfferedShareNotification.php | 46 ++++++ .../CanceledByDealershipNotification.php | 2 +- .../CanceledBySelfNotification.php | 2 +- .../TableAcceptedNotification.php | 2 +- .../TableAcceptedShareNotification.php | 52 +++++++ .../TableOfferedNotification.php | 43 ++++++ .../TableOfferedShareNotification.php | 40 ++++++ app/Notifications/WaitingListNotification.php | 12 +- .../WelcomeAssistantNotification.php | 6 +- app/Notifications/WelcomeNotification.php | 2 +- database/factories/ApplicationFactory.php | 1 - ...l_status_is_notified_from_applications.php | 28 ++++ .../views/application/invitees.blade.php | 10 +- resources/views/dashboard.blade.php | 119 +++++++++++++-- resources/views/table/confirm.blade.php | 4 +- 27 files changed, 727 insertions(+), 182 deletions(-) create mode 100644 app/Enums/StatusNotificationResult.php delete mode 100644 app/Notifications/AcceptedNotification.php rename app/Notifications/{OnHoldNotification.php => AlternateTableOfferedNotification.php} (81%) create mode 100644 app/Notifications/AlternateTableOfferedShareNotification.php create mode 100644 app/Notifications/TableAcceptedShareNotification.php create mode 100644 app/Notifications/TableOfferedNotification.php create mode 100644 app/Notifications/TableOfferedShareNotification.php create mode 100644 database/migrations/2023_05_16_192248_remove_mail_status_is_notified_from_applications.php diff --git a/app/Enums/ApplicationStatus.php b/app/Enums/ApplicationStatus.php index b135cce..0fe3481 100644 --- a/app/Enums/ApplicationStatus.php +++ b/app/Enums/ApplicationStatus.php @@ -7,6 +7,7 @@ enum ApplicationStatus: string case Canceled = 'canceled'; case Open = 'open'; case Waiting = 'waiting'; + case TableAssigned = 'table_assigned'; case TableOffered = 'table_offered'; case TableAccepted = 'table_accepted'; case CheckedIn = 'checked_in'; diff --git a/app/Enums/StatusNotificationResult.php b/app/Enums/StatusNotificationResult.php new file mode 100644 index 0000000..ed4802d --- /dev/null +++ b/app/Enums/StatusNotificationResult.php @@ -0,0 +1,13 @@ +columns()->schema([ Forms\Components\Textarea::make('wanted_neighbors') - ->label('Wanted') + ->label('Wanted Neighbors') ->maxLength(65535), ]), Forms\Components\Textarea::make('comment') @@ -63,13 +68,13 @@ public static function form(Form $form): Form "canceled" => "Canceled", "open" => "Open", "waiting" => "Waiting", + "table_assigned" => "Table assigned (Open)", "table_offered" => "Table offered", "table_accepted" => "Table accepted", - "checked_in" => "Checked in (Onsite)" - ])->required()->reactive(), + "checked_in" => "Checked in (on-site)" + ])->disablePlaceholderSelection()->required()->reactive(), Forms\Components\TextInput::make('table_number') ->maxLength(255), - Forms\Components\Toggle::make('is_notified')->label('Notification sent'), ]), Forms\Components\Fieldset::make('Relationships')->inlineLabel()->columns(1)->schema([ @@ -79,24 +84,24 @@ public static function form(Form $form): Form Forms\Components\Select::make('user_id')->searchable()->relationship('user', 'name') ->required(), Forms\Components\Select::make('parent')->searchable()->options(Application::getEligibleParents()->pluck('name', 'id')) - ->hidden(fn(\Closure $get) => $get('type') === ApplicationType::Dealer->value) - ->required(fn(\Closure $get) => $get('type') !== ApplicationType::Dealer->value), + ->hidden(fn (\Closure $get) => $get('type') === ApplicationType::Dealer->value) + ->required(fn (\Closure $get) => $get('type') !== ApplicationType::Dealer->value), Forms\Components\Select::make('table_type_requested')->relationship('requestedTable', 'name') - ->hidden(fn(\Closure $get) => $get('type') !== ApplicationType::Dealer->value) - ->required(fn(\Closure $get) => $get('type') === ApplicationType::Dealer->value), + ->hidden(fn (\Closure $get) => $get('type') !== ApplicationType::Dealer->value) + ->required(fn (\Closure $get) => $get('type') === ApplicationType::Dealer->value), Forms\Components\Select::make('table_type_assigned')->relationship('assignedTable', 'name') - ->hidden(fn(\Closure $get) => $get('type') !== ApplicationType::Dealer->value) - ->nullable(fn(\Closure $get) => $get('status') !== ApplicationStatus::TableOffered->value), + ->hidden(fn (\Closure $get) => $get('type') !== ApplicationType::Dealer->value) + ->nullable(fn (\Closure $get) => $get('status') !== ApplicationStatus::TableOffered->value), ]), Forms\Components\Fieldset::make('Dates')->inlineLabel()->columns(1)->schema([ - Forms\Components\Placeholder::make('offer_sent_at')->content(fn(?Application $record): string => $record?->offer_sent_at?->diffForHumans() ?? '-'), - Forms\Components\Placeholder::make('offer_accepted_at')->content(fn(?Application $record): string => $record?->offer_accepted_at?->diffForHumans() ?? '-'), - Forms\Components\Placeholder::make('waiting_at')->content(fn(?Application $record): string => $record?->waiting_at?->diffForHumans() ?? '-'), - Forms\Components\Placeholder::make('checked_in_at')->content(fn(?Application $record): string => $record?->checked_in_at?->diffForHumans() ?? '-'), - Forms\Components\Placeholder::make('canceled_at')->content(fn(?Application $record): string => $record?->canceled_at?->diffForHumans() ?? '-'), - Forms\Components\Placeholder::make('updated_at')->content(fn(?Application $record): string => $record?->updated_at?->diffForHumans() ?? '-'), - Forms\Components\Placeholder::make('created_at')->content(fn(?Application $record): string => $record?->created_at?->diffForHumans() ?? '-'), + Forms\Components\Placeholder::make('offer_sent_at')->content(fn (?Application $record): string => $record?->offer_sent_at?->diffForHumans() ?? '-'), + Forms\Components\Placeholder::make('offer_accepted_at')->content(fn (?Application $record): string => $record?->offer_accepted_at?->diffForHumans() ?? '-'), + Forms\Components\Placeholder::make('waiting_at')->content(fn (?Application $record): string => $record?->waiting_at?->diffForHumans() ?? '-'), + Forms\Components\Placeholder::make('checked_in_at')->content(fn (?Application $record): string => $record?->checked_in_at?->diffForHumans() ?? '-'), + Forms\Components\Placeholder::make('canceled_at')->content(fn (?Application $record): string => $record?->canceled_at?->diffForHumans() ?? '-'), + Forms\Components\Placeholder::make('updated_at')->content(fn (?Application $record): string => $record?->updated_at?->diffForHumans() ?? '-'), + Forms\Components\Placeholder::make('created_at')->content(fn (?Application $record): string => $record?->created_at?->diffForHumans() ?? '-'), ]), ]), ]); @@ -111,17 +116,31 @@ public static function table(Table $table): Table Tables\Columns\BadgeColumn::make('status')->enum(ApplicationStatus::cases())->formatStateUsing(function (Application $record) { return $record->status->name; })->colors([ - 'secondary', - 'success' => ApplicationStatus::TableAccepted->value, - 'danger' => ApplicationStatus::Canceled->value - ]), + 'secondary', + 'success' => ApplicationStatus::TableAccepted->value, + 'danger' => ApplicationStatus::Canceled->value + ]), Tables\Columns\TextColumn::make('requestedTable.name'), - Tables\Columns\TextColumn::make('assignedTable.name'), + // FIXME: It is currently not possible to select 'null' to clear the assigned table here, therefore placeholder selection has been disabled. + Tables\Columns\SelectColumn::make('table_type_assigned')->options(TableType::pluck('name', 'id')->toArray())->disablePlaceholderSelection(), Tables\Columns\TextColumn::make('type')->formatStateUsing(function (string $state) { return ucfirst($state); })->sortable(), - Tables\Columns\TextColumn::make('display_name')->searchable(), Tables\Columns\TextInputColumn::make('table_number')->sortable()->searchable(), + Tables\Columns\IconColumn::make('is_ready')->getStateUsing(function (Application $record) { + return $record->isReady(); + })->boolean(), + Tables\Columns\TextColumn::make('dlrshp')->getStateUsing(function (Application $record) { + return $record->parent ?: $record->id; + })->sortable(query: function (Builder $query, string $direction): Builder { + return $query + ->orderBy(DB::raw('IF(`type` = \'dealer\', `id`,`parent`)'), $direction); + })->searchable(query: function (Builder $query, string $search): Builder { + return $query + ->where('id', '=', $search) + ->orWhere('parent', '=', $search); + }), + Tables\Columns\TextColumn::make('display_name')->searchable(), Tables\Columns\IconColumn::make('wanted_neighbors')->label('N Wanted')->default(false)->boolean(), Tables\Columns\IconColumn::make('comment')->default(false)->boolean(), Tables\Columns\IconColumn::make('is_afterdark') @@ -136,19 +155,15 @@ public static function table(Table $table): Table ->label('Wallseat') ->sortable() ->boolean(), - Tables\Columns\IconColumn::make('is_notified') - ->label('Notification sent') - ->sortable() - ->boolean(), Tables\Columns\TextColumn::make('created_at') ->dateTime(), ]) ->filters([ - Tables\Filters\Filter::make('parent')->query(fn(Builder $query): Builder => $query->where('type', 'dealer'))->label('Only Dealerships'), - Tables\Filters\Filter::make('assignedTable')->query(fn(Builder $query): Builder => $query->whereNull('table_type_assigned'))->label('Missing assigned table'), - Tables\Filters\Filter::make('table_number')->query(fn(Builder $query): Builder => $query->whereNull('table_number'))->label('Missing table number'), - Tables\Filters\Filter::make('is_afterdark')->query(fn(Builder $query): Builder => $query->where('is_afterdark', '=', '1'))->label('Is Afterdark'), - Tables\Filters\Filter::make('table_assigned')->query(fn(Builder $query): Builder => $query->whereNotNull('offer_sent_at'))->label('Table assigned'), + Tables\Filters\Filter::make('parent')->query(fn (Builder $query): Builder => $query->where('type', 'dealer'))->label('Only Dealerships'), + Tables\Filters\Filter::make('assignedTable')->query(fn (Builder $query): Builder => $query->whereNull('table_type_assigned'))->label('Missing assigned table'), + Tables\Filters\Filter::make('table_number')->query(fn (Builder $query): Builder => $query->whereNull('table_number'))->label('Missing table number'), + Tables\Filters\Filter::make('is_afterdark')->query(fn (Builder $query): Builder => $query->where('is_afterdark', '=', '1'))->label('Is Afterdark'), + Tables\Filters\Filter::make('table_assigned')->query(fn (Builder $query): Builder => $query->whereNotNull('offer_sent_at'))->label('Table assigned'), ]) ->actions([ Tables\Actions\EditAction::make(), @@ -157,9 +172,63 @@ public static function table(Table $table): Table Tables\Actions\DeleteBulkAction::make(), Tables\Actions\BulkAction::make('Send status notification') ->action(function (Collection $records): void { + $resultsType = StatusNotificationResult::class; + $results = array_fill_keys( + array_map( + fn (StatusNotificationResult $r) => $r->name, + $resultsType::cases() + ), + 0 + ); foreach ($records as $record) { - ApplicationController::sendStatusNotification($record); + $result = ApplicationController::sendStatusNotification($record); + $results[$result->name] += 1; } + + $statusCounts = ''; + $totalSentCount = 0; + $frontendNotification = Notification::make(); + + foreach ($results as $statusName => $count) { + switch ($statusName) { + case StatusNotificationResult::Accepted->name: + $statusCounts .= "
  • {$count} notified about being accepted with requested table
  • "; + $totalSentCount += $count; + break; + case StatusNotificationResult::OnHold->name: + $statusCounts .= "
  • {$count} notified about offered table differing from requested table (on-hold)
  • "; + $totalSentCount += $count; + break; + case StatusNotificationResult::WaitingList->name: + $statusCounts .= "
  • {$count} notified about waiting list
  • "; + $totalSentCount += $count; + break; + case StatusNotificationResult::SharesInvalid->name: + $statusCounts .= "
  • {$count} not notified because shares/assistants not assigned to same table
  • "; + break; + case StatusNotificationResult::StatusNotApplicable->name: + $statusCounts .= "
  • {$count} not notified because status not applicable
  • "; + break; + case StatusNotificationResult::NotDealer->name: + $statusCounts .= "
  • {$count} not directly notified because share/assistant
  • "; + break; + default: + $statusCounts .= "
  • {$count} with unknown status {$statusName}
  • "; + break; + } + } + + if ($totalSentCount > 0) { + $frontendNotification->title("{$totalSentCount} bulk notifications sent") + ->success(); + } else { + $frontendNotification->title("No bulk notifications sent") + ->warning(); + } + + $frontendNotification->body("")->persistent()->send(); }) ->requiresConfirmation() ->icon('heroicon-o-mail'), diff --git a/app/Filament/Resources/ApplicationResource/Pages/EditApplication.php b/app/Filament/Resources/ApplicationResource/Pages/EditApplication.php index d019c7d..7e7ee5e 100644 --- a/app/Filament/Resources/ApplicationResource/Pages/EditApplication.php +++ b/app/Filament/Resources/ApplicationResource/Pages/EditApplication.php @@ -2,8 +2,11 @@ namespace App\Filament\Resources\ApplicationResource\Pages; +use App\Enums\StatusNotificationResult; use App\Filament\Resources\ApplicationResource; use App\Http\Controllers\Applications\ApplicationController; +use App\Models\Application; +use Filament\Notifications\Notification; use Filament\Pages\Actions; use Filament\Resources\Pages\EditRecord; @@ -22,9 +25,57 @@ protected function getActions(): array ]; } + public function getRecord(): Application + { + return parent::getRecord(); + } + public function sendStatusNotification() { - ApplicationController::sendStatusNotification($this->getRecord()); - $this->refreshFormData(['is_notified']); + $application = $this->getRecord(); + $user = $application->user()->first(); + $result = ApplicationController::sendStatusNotification($application); + $frontendNotification = Notification::make(); + + switch ($result) { + case StatusNotificationResult::Accepted: + $frontendNotification->title('Notification sent') + ->body("Notified application {$application->id} of user {$user->name} about being accepted with their requested table type.") + ->success(); + break; + case StatusNotificationResult::OnHold: + $frontendNotification->title('Notification sent') + ->body("Notified application {$application->id} of user {$user->name} about being offered a different table type than they requested (on-hold).") + ->success(); + break; + case StatusNotificationResult::WaitingList: + $frontendNotification->title('Notification sent') + ->body("Notified application {$application->id} of user {$user->name} about being put on the waiting list.") + ->success(); + break; + case StatusNotificationResult::SharesInvalid->name: + $frontendNotification->title('Notification not sent') + ->body("Application not notified because some uncanceled shares have not been assigned to the same table number!") + ->danger(); + break; + case StatusNotificationResult::StatusNotApplicable: + $frontendNotification->title('Notification not sent') + ->body("No applicable notification for current status '{$application->status->value}' or type '{$application->type->value}' of application {$application->id} of user {$user->name}.") + ->warning(); + break; + case StatusNotificationResult::NotDealer: + $frontendNotification->title('Notification not sent') + ->body("Did not notify application {$application->id} of user {$user->name} because they are of type {$application->type->value}.") + ->danger(); + break; + default: + $frontendNotification->title('Error') + ->body("Unexpected return value from ApplicationController::sendStatusNotification! Please inform the developers: [application={$application->id},result={$result->name}]") + ->danger(); + break; + } + + $frontendNotification->persistent()->send(); + $this->refreshFormData(['status']); } } diff --git a/app/Filament/Resources/UserResource/Pages/EditUser.php b/app/Filament/Resources/UserResource/Pages/EditUser.php index c7b6838..3aab228 100644 --- a/app/Filament/Resources/UserResource/Pages/EditUser.php +++ b/app/Filament/Resources/UserResource/Pages/EditUser.php @@ -33,6 +33,6 @@ public function bookPackage() public function removePackage() { RegSysClientController::removePackage($this->getRecord()->reg_id, $this->getRecord()->application()->first()->assignedTable()->first()); - $this->refreshFormData(['is_notified']); + $this->refreshFormData(['packages booked']); } } diff --git a/app/Http/Controllers/Applications/ApplicationController.php b/app/Http/Controllers/Applications/ApplicationController.php index 1b37689..9595f74 100644 --- a/app/Http/Controllers/Applications/ApplicationController.php +++ b/app/Http/Controllers/Applications/ApplicationController.php @@ -4,15 +4,18 @@ use App\Enums\ApplicationStatus; use App\Enums\ApplicationType; +use App\Enums\StatusNotificationResult; use App\Http\Controllers\Controller; use App\Http\Controllers\ProfileController; use App\Http\Requests\ApplicationRequest; use App\Models\Application; use App\Models\TableType; -use App\Notifications\AcceptedNotification; +use App\Notifications\AlternateTableOfferedNotification; +use App\Notifications\AlternateTableOfferedShareNotification; use App\Notifications\CanceledByDealershipNotification; use App\Notifications\CanceledBySelfNotification; -use App\Notifications\OnHoldNotification; +use App\Notifications\TableOfferedNotification; +use App\Notifications\TableOfferedShareNotification; use App\Notifications\WaitingListNotification; use App\Notifications\WelcomeAssistantNotification; use App\Notifications\WelcomeNotification; @@ -38,7 +41,7 @@ public function store(ApplicationRequest $request) { $application = $request->act(); if ($application && $application->getStatus() === ApplicationStatus::Open) { - switch($application->type) { + switch ($application->type) { case ApplicationType::Dealer: case ApplicationType::Share: \Auth::user()->notify(new WelcomeNotification()); @@ -77,14 +80,17 @@ public function update(ApplicationRequest $request) public function delete() { + $application = \Auth::user()->application; + abort_if($application->status === ApplicationStatus::TableAccepted || $application->status === ApplicationStatus::CheckedIn, 403, 'Applications which have accepted their table may no longer be canceled.'); return view('application.delete', [ - "application" => \Auth::user()->application, + "application" => $application, ]); } public function destroy() { $application = \Auth::user()->application; + abort_if($application->status === ApplicationStatus::TableAccepted || $application->status === ApplicationStatus::CheckedIn, 403, 'Applications which have accepted their table may no longer be canceled.'); foreach ($application->children()->get() as $child) { $child->update([ 'canceled_at' => now(), @@ -132,27 +138,64 @@ public function exportCsv() return response()->stream($callback, 200, $headers)->sendContent(); } - public static function sendStatusNotification(Application $application) { + public static function sendStatusNotification(Application $application): StatusNotificationResult + { $user = $application->user()->first(); $status = $application->getStatus(); - if (!$application->is_notified){ - switch ($status){ - case ApplicationStatus::TableOffered: - $tableData = $application->assignedTable()->first()->name . ' - ' . $application->assignedTable()->first()->price/100 . ' EUR'; - if ($application->assignedTable()->first()->name === $application->requestedTable()->first()->name) { - $user->notify(new AcceptedNotification($tableData)); + if ($application->type !== ApplicationType::Dealer) { + Log::info("Not sending accepted notification for user {$user->id} for application {$application->id} since they are not a Dealer."); + return StatusNotificationResult::NotDealer; + } else { + switch ($status) { + case ApplicationStatus::TableAssigned: + // Do not send offer to dealerships where not all shares are accepted + if (!$application->isReady()) { + Log::info("Not sending accepted notification for user {$user->id} for application {$application->id} since not all Shares or Assistants have been set to TableOffered or Canceled or been assigned the same table number during review."); + return StatusNotificationResult::SharesInvalid; + } + + if ($application->table_type_assigned === $application->table_type_requested) { + Log::info("Sending accepted notification for table {$application->table_number} (requested: {$application->table_type_requested} | assigned: {$application->table_type_assigned}) to user {$user->id} for application {$application->id}."); + $user->notify(new TableOfferedNotification()); + foreach ($application->children()->get() as $child) { + if ($child->type === ApplicationType::Share) { + $child->user()->first()->notify(new TableOfferedShareNotification()); + } + } + $application->status = ApplicationStatus::TableOffered; + return StatusNotificationResult::Accepted; } else { - $user->notify(new OnHoldNotification($tableData)); + Log::info("Sending on-hold notification for table {$application->table_number} (requested: {$application->table_type_requested} | assigned: {$application->table_type_assigned}) to user {$user->id} for application {$application->id}."); + $assignedTable = $application->assignedTable()->first(); + $user->notify(new AlternateTableOfferedNotification($assignedTable->name, $assignedTable->price)); + foreach ($application->children()->get() as $child) { + if ($child->type === ApplicationType::Share) { + $child->user()->first()->notify(new AlternateTableOfferedShareNotification($assignedTable->name, $assignedTable->price)); + } + } + $application->status = ApplicationStatus::TableOffered; + return StatusNotificationResult::OnHold; } - $application->setIsNotified(true); - break; - case ApplicationStatus::Waiting: + case ApplicationStatus::Open: + // Do not send dealerships to waiting list if some shares/assistants have a table number + if (!$application->isReady()) { + Log::info("Not sending application {$application->id} of user {$user->id} to waiting list since some Shares or Assistants have been assigned a table number."); + return StatusNotificationResult::SharesInvalid; + } + + Log::info("Sending waiting list notification to user {$user->id} for application {$application->id}."); $user->notify(new WaitingListNotification()); - $application->setIsNotified(true); - break; + foreach ($application->children()->get() as $child) { + if ($child->type === ApplicationType::Share) { + $child->user()->first()->notify(new WaitingListNotification()); + } + } + $application->status = ApplicationStatus::Waiting; + return StatusNotificationResult::WaitingList; default: - break; + Log::info("Not sending notification to user {$user->id} because application {$application->id} is not in an applicable status."); + return StatusNotificationResult::StatusNotApplicable; } } } diff --git a/app/Http/Controllers/Applications/InviteesController.php b/app/Http/Controllers/Applications/InviteesController.php index 3315f53..ffe26ba 100644 --- a/app/Http/Controllers/Applications/InviteesController.php +++ b/app/Http/Controllers/Applications/InviteesController.php @@ -7,6 +7,7 @@ use App\Http\Requests\InviteeRemovalRequest; use App\Models\Application; use App\Notifications\CanceledByDealershipNotification; +use Carbon\Carbon; class InviteesController extends Controller { @@ -20,19 +21,19 @@ public function view() /** * Create invite codes if not existing yet */ - if(is_null($application->invite_code_shares)) { - $application->update(['invite_code_shares' => "dealers-".\Str::random()]); + if (is_null($application->invite_code_shares)) { + $application->update(['invite_code_shares' => "dealers-" . \Str::random()]); } - if(is_null($application->invite_code_assistants)) { - $application->update(['invite_code_assistants' => "assistant-".\Str::random()]); + if (is_null($application->invite_code_assistants)) { + $application->update(['invite_code_assistants' => "assistant-" . \Str::random()]); } - return view('application.invitees',[ + return view('application.invitees', [ 'application' => $application, 'currentSeats' => $application->children()->whereNotNull('canceled_at')->count() + 1, 'maxSeats' => $application->requestedTable->seats, - "assistants" => $application->children()->where('type',ApplicationType::Assistant)->with('user')->get(), - "shares" => $application->children()->where('type',ApplicationType::Share)->with('user')->get(), + "assistants" => $application->children()->where('type', ApplicationType::Assistant)->with('user')->get(), + "shares" => $application->children()->where('type', ApplicationType::Share)->with('user')->get(), "shares_count" => $application->getAvailableShares(), "assistants_count" => $application->getAvailableAssistants(), "shares_active_count" => $application->getActiveShares(), @@ -43,6 +44,7 @@ public function view() public function destroy(InviteeRemovalRequest $request) { $invitee = Application::findOrFail($request->get('invitee_id')); + abort_if(!Carbon::parse(config('ef.reg_end_date'))->isFuture() && $invitee->type !== ApplicationType::Assistant, 403, 'Only assistants may be modified once the registration period is over.'); $invitee->update([ "type" => ApplicationType::Dealer, @@ -56,8 +58,8 @@ public function destroy(InviteeRemovalRequest $request) public function regenerateKeys() { $user = \Auth::user()->application->update([ - 'invite_code_assistants' => "assistant-".\Str::random(), - 'invite_code_shares' => "dealers-".\Str::random() + 'invite_code_assistants' => "assistant-" . \Str::random(), + 'invite_code_shares' => "dealers-" . \Str::random() ]); return \Redirect::route('applications.invitees.view'); } diff --git a/app/Http/Controllers/DashboardController.php b/app/Http/Controllers/DashboardController.php index 0ca4063..e9c103b 100644 --- a/app/Http/Controllers/DashboardController.php +++ b/app/Http/Controllers/DashboardController.php @@ -2,12 +2,21 @@ namespace App\Http\Controllers; +use App\Http\Controllers\Client\RegSysClientController; +use Illuminate\Support\Facades\Log; + class DashboardController extends Controller { public function __invoke() { + $user = \Auth::user(); + + $application = $user->application; + $efRegistration = RegSysClientController::getSingleReg($user->reg_id); + return view('dashboard',[ - "application" => \Auth::user()->application + "application" => $application, + "efRegistrationStatus" => $efRegistration ? $efRegistration['status'] : false, ]); } } diff --git a/app/Http/Controllers/TableVerifyController.php b/app/Http/Controllers/TableVerifyController.php index 21e2562..e5db8b6 100644 --- a/app/Http/Controllers/TableVerifyController.php +++ b/app/Http/Controllers/TableVerifyController.php @@ -7,6 +7,7 @@ use App\Http\Controllers\Client\RegSysClientController; use App\Models\TableType; use App\Notifications\TableAcceptedNotification; +use App\Notifications\TableAcceptedShareNotification; use Illuminate\Http\Request; class TableVerifyController extends Controller @@ -25,14 +26,7 @@ public function view() abort_if($application->type !== ApplicationType::Dealer, 403, 'Shares and Assistants cannot manage this.'); - if ($application->status === ApplicationStatus::TableOffered && $application->is_notified) { - return view('table.confirm', [ - 'application' => $application, - 'table_type_requested' => TableType::find($application->table_type_requested), - 'table_type_assigned' => TableType::find($application->table_type_assigned), - 'table_number' => $application->table_number, - ]); - } else if ($application->status === ApplicationStatus::TableAccepted && $application->is_notified) { + if ($application->status === ApplicationStatus::TableOffered) { return view('table.confirm', [ 'application' => $application, 'table_type_requested' => TableType::find($application->table_type_requested), @@ -40,18 +34,27 @@ public function view() 'table_number' => $application->table_number, ]); } else { - return view('dashboard'); + return \Redirect::route('dashboard'); } } public function update(Request $request) { $application = \Auth::user()->application; + + abort_if($application->status !== ApplicationStatus::TableOffered, 403, 'No table offer available to be accepted.'); + abort_if($application->type !== ApplicationType::Dealer, 403, 'Shares and Assistants cannot manage this.'); + $assignedTable = $application->assignedTable()->first(); if (RegSysClientController::bookPackage(\Auth::user()->reg_id, $assignedTable)) { $application->setStatusAttribute(ApplicationStatus::TableAccepted); \Auth::user()->notify(new TableAcceptedNotification($assignedTable->name, $application->table_number, $assignedTable->price)); + foreach ($application->children()->get() as $child) { + if ($child->type === ApplicationType::Share) { + $child->user()->first()->notify(new TableAcceptedShareNotification($assignedTable->name, $application->table_number, $assignedTable->price)); + } + } return \Redirect::route('table.confirm')->with('table-confirmation-successful'); } else { return \Redirect::route('table.confirm')->with('table-confirmation-error'); @@ -59,14 +62,10 @@ public function update(Request $request) } - public function delete(Request $request) - { - $application = \Auth::user()->application; - - if (RegSysClientController::removePackage(\Auth::user()->reg_id, TableType::find($application->assignedTable()->first()))) { - // TODO - } else { - // TODO - } - } + /* + * Delete method does not need to be implemented because: + * - Once accepted, unpaid tables can only be canceled by contacting the team. + * - Paid tables cannot be canceled at all (at best, transfer via team may be possible). + */ + // public function delete(Request $request) {} } diff --git a/app/Models/Application.php b/app/Models/Application.php index 6054a27..0e0ca26 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -6,6 +6,7 @@ use App\Enums\ApplicationType; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Facades\Log; use Illuminate\Validation\ValidationException; use Symfony\Component\Console\Helper\Table; @@ -33,13 +34,66 @@ class Application extends Model "checked_in_at" => "datetime", "offer_sent_at" => "datetime", "offer_accepted_at" => "datetime", - "is_notified" => "boolean", ]; protected $attributes = [ "table_type_requested" => 2 ]; + protected static function boot() + { + parent::boot(); + // Automatically update application status from Waiting to TableAssigned on table type/number change + // to allow accepting waiting dealerships via table assignment + static::updating(function (Application $model) { + if ( // table number or type was modified + ($model->isDirty('table_type_assigned') || $model->isDirty('table_number')) + // table number must not be empty + && !empty($model->table_number) + // table type must be assigned for dealer applications + && ($model->type !== ApplicationType::Dealer || !empty($model->table_type_assigned)) + // status is applicable for automatic change (only Waiting) + && $model->status === ApplicationStatus::Waiting + ) { + Log::info("Changing status of application {$model->id} from {$model->status->name} to TableAssigned due to table type/name change."); + $model->status = ApplicationStatus::TableAssigned; + } + }); + + // Update the status of child applications on parent changing to TableOffered/Waiting/TableAccepted + static::updated(function (Application $model) { + if ( + $model->type === ApplicationType::Dealer + && ( + ($model->wasChanged('offer_accepted_at') && !empty($model->offer_accepted_at)) + || ($model->wasChanged('offer_sent_at') && !empty($model->offer_sent_at)) + || ($model->wasChanged('waiting_at')) + ) + ) { + foreach ($model->children()->get() as $child) { + if ($child->status !== ApplicationStatus::Canceled) { + Log::info("Changing status of application {$child->id} to {$model->status->name} due to parent application {$model->id} having changed to this status."); + $child->status = $model->status; + } + } + } + }); + + // Update table number of Assistants when their parent's table number is updated + // DO NOT do this for Shares as it would automatically set them to TableAssigned without review! + static::updated(function (Application $model) { + if ($model->type === ApplicationType::Dealer && $model->wasChanged('table_number')) { + foreach ($model->children()->get() as $child) { + if ($child->status !== ApplicationStatus::Canceled && $child->type === ApplicationType::Assistant) { + Log::info("Changing table number of assistant application {$child->id} to {$model->table_number} due to parent application {$model->id} change."); + $child->table_number = $model->table_number; + $child->update(); + } + } + } + }); + } + public function user() { return $this->belongsTo(User::class); @@ -69,20 +123,19 @@ public function getStatus() { if (!is_null($this->canceled_at)) { return ApplicationStatus::Canceled; - } - if (!is_null($this->checked_in_at)) { + } elseif (!is_null($this->checked_in_at)) { return ApplicationStatus::CheckedIn; - } - if (!is_null($this->waiting_at)) { + } elseif (!is_null($this->waiting_at)) { return ApplicationStatus::Waiting; - } - if (!is_null($this->offer_accepted_at)) { + } elseif (!is_null($this->offer_accepted_at)) { return ApplicationStatus::TableAccepted; - } - if (!is_null($this->offer_sent_at)) { + } elseif (!is_null($this->offer_sent_at)) { return ApplicationStatus::TableOffered; + } elseif (($this->type !== ApplicationType::Dealer || !is_null($this->table_type_assigned)) && !empty($this->table_number)) { + return ApplicationStatus::TableAssigned; + } else { + return ApplicationStatus::Open; } - return ApplicationStatus::Open; } public function isActive() @@ -159,6 +212,12 @@ public function setStatusAttribute(ApplicationStatus|string $status) if (is_string($status)) { $status = ApplicationStatus::tryFrom($status); } + + // Don't reset timestamps when application is saved without status change! + if ($this->getStatus() === $status) { + return; + } + if ($status === ApplicationStatus::Canceled) { $this->update([ 'offer_accepted_at' => null, @@ -171,6 +230,16 @@ public function setStatusAttribute(ApplicationStatus|string $status) ]); } + // TableAssigned is technically identical to open just with a table number assigned. + if ($status === ApplicationStatus::TableAssigned) { + $this->update([ + 'offer_accepted_at' => null, + 'offer_sent_at' => null, + 'waiting_at' => null, + 'canceled_at' => null, + ]); + } + if ($status === ApplicationStatus::Open) { $this->update([ 'offer_accepted_at' => null, @@ -185,6 +254,7 @@ public function setStatusAttribute(ApplicationStatus|string $status) $this->update([ 'offer_accepted_at' => null, 'offer_sent_at' => null, + 'table_number' => null, 'waiting_at' => now(), 'canceled_at' => null, ]); @@ -204,6 +274,7 @@ public function setStatusAttribute(ApplicationStatus|string $status) 'offer_accepted_at' => now(), 'waiting_at' => null, 'canceled_at' => null, + 'checked_in_at' => null, ]); } @@ -264,7 +335,7 @@ public static function getAllApplicationsForExport() 'is_wallseat', 't1.name AS table_type_requested', 't2.name AS table_type_assigned', - 'is_notified', + '\'n/a\' AS is_notified', // keeping this to not mess with the column count 'applications.created_at AS app_created_at', 'applications.updated_at AS app_updated_at', 'waiting_at', @@ -279,11 +350,37 @@ public static function getAllApplicationsForExport() return json_decode(json_encode($applications), true); } - public function setIsNotified($isNotified) + /** + * An application is considered ready if all related applications (parent/children) share the + * same status (canceled child applications are ignored for this) and table number and the + * application itself is not canceled. + */ + public function isReady(): bool { - $this->is_notified = $isNotified; - $this->save(); - } + if ($this->status === ApplicationStatus::Canceled) { + return false; + } -} + if ($this->type !== ApplicationType::Dealer) { + $dealership = $this->parent()->get()->first(); + } else { + $dealership = $this; + } + + foreach ($dealership->children()->get() as $child) { + if ($child->status !== ApplicationStatus::Canceled && $child->status !== $dealership->status) { + return false; + } + // table numbers must be identical + if ( + $child->table_number !== $dealership->table_number + // table numbers '' and null should be considered identical + && !(empty($dealership->table_number) && empty($child->table_number)) + ) { + return false; + } + } + return true; + } +} diff --git a/app/Notifications/AcceptedNotification.php b/app/Notifications/AcceptedNotification.php deleted file mode 100644 index a9fceed..0000000 --- a/app/Notifications/AcceptedNotification.php +++ /dev/null @@ -1,46 +0,0 @@ -tableAssigned = $tableAssigned; - } - - public function via(object $notifiable): array - { - return ['mail']; - } - - public function toMail(object $notifiable): MailMessage - { - return (new MailMessage) - ->greeting("Dear " . $notifiable->name . ",") - ->subject('Approval Phase: Accepted') - ->line('We are thrilled to inform you that your application for a dealership at ' . config('ef.con_name') . ' has been accepted! Congratulations!') - ->line(new HtmlString('To confirm your placement as a dealer at ' . config('ef.con_name') . ', please click on the approval button below. By clicking on this button, you are agreeing to the Dealers\' Den\'s terms and conditions, and the payment process will be initiated.')) - ->action('I confirm my selected Dealership Package.', url('/table/confirm')) - ->line(new HtmlString('By approving, your Eurofurence event registration will be updated to include the fee for the assigned dealership. All payments must be handled through the Eurofurence registration system, available at ' . config('ef.idp_url') . '. Please note that you are required to pay all fees, including the Eurofurence event registration fee, within ' . config('ef.payment_timeframe') . ' of receiving this email to secure your placement as a dealer. If payment is overdue, Dealers\' Den management may void your placement and offer the space to the next dealer on the waiting list.')) - ->line('Upon completion of payment, we will assign a specific table to your dealership, based on your request and space availability. Although all placements are not final until the start of the convention, you will be sent an email containing the preliminary dealership table assignment for information.') - ->line(new HtmlString('If you have any questions or concerns regarding the payment or subsequent processes, please contact Dealers\' Den management via ' . config('ef.dealers_email') . '.')) - ->line('Thank you again for your interest and participation in ' . config('ef.con_name') . ' Dealers\' Den! We are looking forward to seeing you and your beautiful artwork and items on display at the convention, and have no doubt that it will be a huge hit among our attendees.') - ->salutation(new HtmlString('Best regards,
    the Eurofurence Dealers\' Den Team')); - } - - public function toArray(object $notifiable): array - { - return []; - } -} diff --git a/app/Notifications/OnHoldNotification.php b/app/Notifications/AlternateTableOfferedNotification.php similarity index 81% rename from app/Notifications/OnHoldNotification.php rename to app/Notifications/AlternateTableOfferedNotification.php index 72fa374..07a0660 100644 --- a/app/Notifications/OnHoldNotification.php +++ b/app/Notifications/AlternateTableOfferedNotification.php @@ -8,15 +8,17 @@ use Illuminate\Notifications\Notification; use Illuminate\Support\HtmlString; -class OnHoldNotification extends Notification implements ShouldQueue +class AlternateTableOfferedNotification extends Notification implements ShouldQueue { use Queueable; public string $tableAssigned; + public int|float $price; - public function __construct(string $tableAssigned) + public function __construct(string $tableAssigned, int|float $price) { $this->tableAssigned = $tableAssigned; + $this->price = $price; } public function via(object $notifiable): array @@ -28,12 +30,12 @@ public function toMail(object $notifiable): MailMessage { return (new MailMessage) ->greeting("Dear " . $notifiable->name . ",") - ->subject('Approval Phase: On Hold') + ->subject(config('ef.con_name') . ' Dealers\' Den - Alternate Table Size') ->line('Thank you for your interest in a Dealership at Eurofurence. We appreciate your application and are excited to have you as a potential dealer at the convention.') ->line('Unfortunately, we regret to inform you that the table size you have applied for is no longer available. However, we have some alternative table sizes we would like to offer that may be suitable for your needs.') ->line('Currently, the following table size is available:') - ->line($this->tableAssigned) - ->action('I confirm and agree to purchase the offered Dealership Package.', url('/table/confirm')) + ->line($this->tableAssigned . ' - ' . $this->price / 100 . ' EUR') + ->action('Review Dealership Package.', url('/table/confirm')) ->line('We understand that this alternative option may not be what you had in mind when applying, but we hope that it will still meet your needs and enable you to participate in the Eurofurence Dealers\' Den. As we have limited space, we will be offering any available table sizes to other applications on the waiting list if we do not receive a response from you within the next seven days. So please let us know within this time frame whether you would like to accept one of the alternative options or decline the offer and be put on the waiting list. To do so, simply reply to this email.') ->line('We apologize for any inconvenience this may have caused you, and we hope to hear back from you soon. Thank you for your understanding and cooperation.') ->salutation(new HtmlString('Best regards,
    the Eurofurence Dealers\' Den Team')); diff --git a/app/Notifications/AlternateTableOfferedShareNotification.php b/app/Notifications/AlternateTableOfferedShareNotification.php new file mode 100644 index 0000000..3786103 --- /dev/null +++ b/app/Notifications/AlternateTableOfferedShareNotification.php @@ -0,0 +1,46 @@ +tableAssigned = $tableAssigned; + $this->price = $price; + } + + public function via(object $notifiable): array + { + return ['mail']; + } + + public function toMail(object $notifiable): MailMessage + { + return (new MailMessage) + ->greeting("Dear " . $notifiable->name . ",") + ->subject(config('ef.con_name') . ' Dealers\' Den - Alternate Table Size') + ->line('Thank you for your interest as share in a Dealership at Eurofurence. We appreciate your application and are excited to have you as a potential dealer at the convention.') + ->line('Unfortunately, we regret to inform you that the table size your Dealership has applied for is no longer available. However, we have offered your Dealership an alternative table size that may be suitable for your needs.') + ->line('Currently, the following table size is available:') + ->line($this->tableAssigned . ' - ' . $this->price / 100 . ' EUR') + ->line('We understand that this alternative option may not be what you had in mind when applying, but we hope that it will still meet your needs and enable you to participate in the Eurofurence Dealers\' Den. Please consult with your Dealership about how you would like to proceed, as they are the ones who will have to accept or decline the offer and pay the abovementioned fee.') + ->salutation(new HtmlString('Best regards,
    the Eurofurence Dealers\' Den Team')); + } + + public function toArray(object $notifiable): array + { + return []; + } +} diff --git a/app/Notifications/CanceledByDealershipNotification.php b/app/Notifications/CanceledByDealershipNotification.php index 7c479cf..c9ca8f1 100644 --- a/app/Notifications/CanceledByDealershipNotification.php +++ b/app/Notifications/CanceledByDealershipNotification.php @@ -24,7 +24,7 @@ public function via($notifiable): array public function toMail($notifiable): MailMessage { return (new MailMessage) - ->subject('Registration Status: Canceled by Dealership') + ->subject(config('ef.con_name') . ' Dealers\' Den - Application Canceled by Dealership') ->greeting("Dear ".$notifiable->name.",") ->line('The dealership you had joined has chosen to remove you from their application, thereby canceling your own application. But don\'t worry, all data you had entered remains in our system, allowing you pick one of the following options without having to reenter everything:') ->line('- Ask the dealership you were part of to allow you to rejoin if you think this happened in error,') diff --git a/app/Notifications/CanceledBySelfNotification.php b/app/Notifications/CanceledBySelfNotification.php index e8f00cf..caf7391 100644 --- a/app/Notifications/CanceledBySelfNotification.php +++ b/app/Notifications/CanceledBySelfNotification.php @@ -24,7 +24,7 @@ public function via($notifiable): array public function toMail($notifiable): MailMessage { return (new MailMessage) - ->subject('Registration Status: Canceled') + ->subject(config('ef.con_name') . ' Dealers\' Den - Application Canceled') ->greeting("Dear ".$notifiable->name.",") ->line('This mail confirms that you have successfully canceled your registration for the Dealers\' Den at this year\'s Eurofurence. We are sorry to see you go and would love to see you back next year.') ->line('Should you change your mind, you can resubmit your application or join another dealership until the end of the application phase.') diff --git a/app/Notifications/TableAcceptedNotification.php b/app/Notifications/TableAcceptedNotification.php index 7a59f73..7f4b251 100644 --- a/app/Notifications/TableAcceptedNotification.php +++ b/app/Notifications/TableAcceptedNotification.php @@ -32,7 +32,7 @@ public function toMail($notifiable): MailMessage { return (new MailMessage) ->greeting("Dear " . $notifiable->name . ",") - ->subject(config('ef.con_name') . ' Dealers\' Den - Confirmation Received and Next Steps') + ->subject(config('ef.con_name') . ' Dealers\' Den - Package Confirmed and Next Steps') ->line('We have received your confirmation for your dealer package at the ' . config('ef.con_name') . ' Dealers\' Den. Thank you for taking the time to review and confirm your table assignment and dealer package details. We are excited to have you join us as a dealer at the convention!') ->line('Your confirmed table assignment details are as follows:') ->line('Table Number: ' . $this->tableNumber) diff --git a/app/Notifications/TableAcceptedShareNotification.php b/app/Notifications/TableAcceptedShareNotification.php new file mode 100644 index 0000000..513b7f6 --- /dev/null +++ b/app/Notifications/TableAcceptedShareNotification.php @@ -0,0 +1,52 @@ +tableAssigned = $tableAssigned; + $this->tableNumber = $tableNumber; + $this->price = $price; + } + + public function via($notifiable): array + { + return ['mail']; + } + + public function toMail($notifiable): MailMessage + { + return (new MailMessage) + ->greeting("Dear " . $notifiable->name . ",") + ->subject(config('ef.con_name') . ' Dealers\' Den - Package Confirmed and Next Steps') + ->line('Your Dealership has successfully confirmed its package at the ' . config('ef.con_name') . ' Dealers\' Den. We are excited to have you join us as a dealer at the convention!') + ->line('The table assignment details are as follows:') + ->line('Table Number: ' . $this->tableNumber) + ->line('Assigned Table Size: ' . $this->tableAssigned) + ->line('Final Dealer Package Price (paid by Dealership): ' . $this->price / 100 . ' EUR') + ->line('As you are part of a Dealership, please be aware that the abovementioned price will be charged to the Dealership and not to you.') + ->line('In the coming weeks, we will be sending you more information about the Dealers\' Den setup, event schedules, and other important details to help you prepare for the convention. Please keep an eye on your email for these updates.') + ->line(new HtmlString('If you have any questions or concerns, feel free to reach out to us at ' . config('ef.dealers_email') . '. We are here to help ensure a smooth and enjoyable experience for all our dealers.')) + ->line('Once again, thank you for your participation in ' . config('ef.con_name') . ' Dealers\' Den. We are looking forward to seeing your amazing art and items showcased at the event!') + ->salutation(new HtmlString('Best regards,
    the Eurofurence Dealers\' Den Team')); + } + + public function toArray($notifiable): array + { + return []; + } +} diff --git a/app/Notifications/TableOfferedNotification.php b/app/Notifications/TableOfferedNotification.php new file mode 100644 index 0000000..9193ac8 --- /dev/null +++ b/app/Notifications/TableOfferedNotification.php @@ -0,0 +1,43 @@ +greeting("Dear " . $notifiable->name . ",") + ->subject(config('ef.con_name') . ' Dealers\' Den - Application Accepted') + ->line('We are thrilled to inform you that your application for a dealership at ' . config('ef.con_name') . ' has been accepted! Congratulations!') + ->line(new HtmlString('To review and confirm your placement as a dealer at ' . config('ef.con_name') . ', please click on the button below. By accepting the offered table, you are agreeing to the Dealers\' Den\'s terms and conditions, and the payment process will be initiated.')) + ->action('Review Dealership Package.', url('/table/confirm')) + ->line(new HtmlString('Once you have confirmed the package, your Eurofurence event registration will be updated to include the fee for the assigned dealership. All payments must be handled through the Eurofurence registration system, available at ' . config('ef.idp_url') . '. Please note that you are required to pay all fees, including the Eurofurence event registration fee, within ' . config('ef.payment_timeframe') . ' of receiving this email to secure your placement as a dealer. If payment is overdue, Dealers\' Den management may void your placement and offer the space to the next dealer on the waiting list.')) + ->line('Although all placements may be subject to change until the start of the convention, you will be sent an email containing the preliminary dealership table assignment for information.') + ->line(new HtmlString('If you have any questions or concerns regarding the payment or subsequent processes, please contact Dealers\' Den management via ' . config('ef.dealers_email') . '.')) + ->line('Thank you again for your interest and participation in ' . config('ef.con_name') . ' Dealers\' Den! We are looking forward to seeing you and your beautiful artwork and items on display at the convention, and have no doubt that it will be a huge hit among our attendees.') + ->salutation(new HtmlString('Best regards,
    the Eurofurence Dealers\' Den Team')); + } + + public function toArray(object $notifiable): array + { + return []; + } +} diff --git a/app/Notifications/TableOfferedShareNotification.php b/app/Notifications/TableOfferedShareNotification.php new file mode 100644 index 0000000..eb36b92 --- /dev/null +++ b/app/Notifications/TableOfferedShareNotification.php @@ -0,0 +1,40 @@ +greeting("Dear " . $notifiable->name . ",") + ->subject(config('ef.con_name') . ' Dealers\' Den - Application Accepted') + ->line('We are thrilled to inform you that the application for the dealership at ' . config('ef.con_name') . ' which you are part of has been accepted! Congratulations!') + ->line('The table still has to be accepted by Dealership who will also be responsible for paying the table fee, so consult with them in case you have any questions.') + ->line('Please note that you are required to pay all fees, including the Eurofurence event registration fee, within ' . config('ef.payment_timeframe') . ' of receiving this email to secure your placement as a dealer. If payment is overdue, Dealers\' Den management may void your placement.') + ->line('Thank you again for your interest and participation in ' . config('ef.con_name') . ' Dealers\' Den! We are looking forward to seeing you and your beautiful artwork and items on display at the convention, and have no doubt that it will be a huge hit among our attendees.') + ->salutation(new HtmlString('Best regards,
    the Eurofurence Dealers\' Den Team')); + } + + public function toArray(object $notifiable): array + { + return []; + } +} diff --git a/app/Notifications/WaitingListNotification.php b/app/Notifications/WaitingListNotification.php index 7022fcf..8a574a8 100644 --- a/app/Notifications/WaitingListNotification.php +++ b/app/Notifications/WaitingListNotification.php @@ -24,12 +24,12 @@ public function via(object $notifiable): array public function toMail(object $notifiable): MailMessage { return (new MailMessage) - ->greeting("Dear ".$notifiable->name.",") - ->subject('Approval Phase: Waiting List') - ->line('Thank you for submitting your application to sell your amazing art and items at the upcoming ' . config('ef.con_name') . ' and your patience while we have been reviewing the dealer applications.') - ->line('Unfortunately, all of our available tables have been filled, and we regret to inform you that we cannot accommodate your application at this time. However, we would like to inform you that your application has been put on the waiting list. Should any slots become available before the convention, we will contact you as soon as possible. Also, there might be last-minute sales of canceled dealership spaces on a first-come, first-serve basis at the convention itself, starting 12:00 (noon) on Monday for unclaimed dealerships. We highly encourage you to drop by in case a spot opens up.') - ->line('Thank you for your understanding and cooperation. We appreciate your interest in the ' . config('ef.con_name') . ' Dealers\' Den, and we hope that we will be able to work with you in the future.') - ->salutation(new HtmlString('Best regards,
    the Eurofurence Dealers\' Den Team')); + ->subject(config('ef.con_name') . ' Dealers\' Den - Application on Waiting List') + ->greeting("Dear " . $notifiable->name . ",") + ->line('Thank you for submitting your application to sell your amazing art and items at the upcoming ' . config('ef.con_name') . ' and your patience while we have been reviewing the dealer applications.') + ->line('Unfortunately, all of our available tables have been filled, and we regret to inform you that we cannot accommodate your application at this time. However, we would like to inform you that your application has been put on the waiting list. Should any slots become available before the convention, we will contact you as soon as possible. Also, there might be last-minute sales of canceled dealership spaces on a first-come, first-serve basis at the convention itself, starting 12:00 (noon) on Monday for unclaimed dealerships. We highly encourage you to drop by in case a spot opens up.') + ->line('Thank you for your understanding and cooperation. We appreciate your interest in the ' . config('ef.con_name') . ' Dealers\' Den, and we hope that we will be able to work with you in the future.') + ->salutation(new HtmlString('Best regards,
    the Eurofurence Dealers\' Den Team')); } public function toArray(object $notifiable): array diff --git a/app/Notifications/WelcomeAssistantNotification.php b/app/Notifications/WelcomeAssistantNotification.php index 7d58766..edd13d6 100644 --- a/app/Notifications/WelcomeAssistantNotification.php +++ b/app/Notifications/WelcomeAssistantNotification.php @@ -24,13 +24,13 @@ public function via($notifiable): array public function toMail($notifiable): MailMessage { return (new MailMessage) - ->greeting("Dear ".$notifiable->name.",") - ->subject('Welcome and Information for Dealer Assistants') + ->subject(config('ef.con_name') . ' Dealers\' Den - Dealer Assistant Information') + ->greeting("Dear " . $notifiable->name . ",") ->line('We are delighted to welcome you as a Dealer Assistant at ' . config('ef.con_name') . ' Dealers\' Den!') ->line('Thank you for accepting your dealerships\' invitation and entering the invitation code. Your support in helping your dealership during setup, teardown, and opening hours is greatly appreciated.') ->line('As a Dealer Assistant, you play a vital role in ensuring the smooth operation and success of your dealer\'s experience at the Dealers\' Den. Your assistance will contribute significantly to the overall experience for both your dealer and the attendees.') ->line('In the coming weeks, we will be sending you more information about the Dealers\' Den setup, event schedules, and other important details to help you prepare for the convention. Please keep an eye on your email for these updates.') - ->line(new HtmlString('If you have any questions or concerns, feel free to reach out to us at ' . config('ef.dealers_email') . '. We are here to help ensure a smooth and enjoyable experience for all our dealers and their assistants.')) + ->line(new HtmlString('If you have any questions or concerns, feel free to reach out to us at ' . config('ef.dealers_email') . '. We are here to help ensure a smooth and enjoyable experience for all our dealers and their assistants.')) ->line('We are looking forward to seeing you at Eurofurence and wish you and your dealer a successful and enjoyable experience!') ->salutation(new HtmlString('Warm regards,
    the Eurofurence Dealers\' Den Team')); } diff --git a/app/Notifications/WelcomeNotification.php b/app/Notifications/WelcomeNotification.php index 0fa0753..c4f5583 100644 --- a/app/Notifications/WelcomeNotification.php +++ b/app/Notifications/WelcomeNotification.php @@ -24,8 +24,8 @@ public function via($notifiable): array public function toMail($notifiable): MailMessage { return (new MailMessage) + ->subject(config('ef.con_name') . ' Dealers\' Den - Application Received') ->greeting("Dear ".$notifiable->name.",") - ->subject('Application Phase: Thank you!') ->line('Thank you for your application for a Dealership at the upcoming Eurofurence. Your interest in being a part of this year\'s Dealers\' Den is very much appreciated.') ->line('We have received your application and will review it once the Dealership application period has ended. We understand that waiting can be difficult, but please know that we are working hard to review all applications in a timely manner. Once we have reviewed all the applications, we will get in touch with you to provide you with all the necessary information about the next steps.') ->line('Thank you in advance for your patience. The Dealers\' Den management is looking forward to reviewing your application.') diff --git a/database/factories/ApplicationFactory.php b/database/factories/ApplicationFactory.php index 115c547..c4d4f43 100644 --- a/database/factories/ApplicationFactory.php +++ b/database/factories/ApplicationFactory.php @@ -28,7 +28,6 @@ public function definition(): array 'is_afterdark' => $this->faker->boolean(), 'is_power' => $this->faker->boolean(), 'is_wallseat' => $this->faker->boolean(), - 'is_notified' => $this->faker->boolean(), 'waiting_at' => Carbon::now(), 'offer_sent_at' => Carbon::now(), 'offer_accepted_at' => Carbon::now(), diff --git a/database/migrations/2023_05_16_192248_remove_mail_status_is_notified_from_applications.php b/database/migrations/2023_05_16_192248_remove_mail_status_is_notified_from_applications.php new file mode 100644 index 0000000..1d021d4 --- /dev/null +++ b/database/migrations/2023_05_16_192248_remove_mail_status_is_notified_from_applications.php @@ -0,0 +1,28 @@ +dropColumn('is_notified'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('applications', function (Blueprint $table) { + $table->boolean('is_notified')->default(false); + }); + } +}; diff --git a/resources/views/application/invitees.blade.php b/resources/views/application/invitees.blade.php index 14b3006..5d9d3bb 100644 --- a/resources/views/application/invitees.blade.php +++ b/resources/views/application/invitees.blade.php @@ -6,7 +6,7 @@

    Manage shares and assistants

    -

    You can give invite codes to users that you wish to either setup a share with or invite as an assistant.
    +

    You can give invite codes to users that you wish to either setup a share with or invite as an assistant. Please note that changes to shares are only possible during the registration period.

    Please note, depending on the space size you selected during registration, the amount of people is limited.

    @@ -30,13 +30,13 @@
    {{ $shares_active_count }}/{{ $shares_count }} Share your space with other dealers