Skip to content

Commit

Permalink
feat: Implement Roster (#3493)
Browse files Browse the repository at this point in the history
  • Loading branch information
CalumTowers authored Feb 29, 2024
1 parent ad23b2a commit 5a9fea9
Show file tree
Hide file tree
Showing 153 changed files with 5,326 additions and 2,228 deletions.
51 changes: 51 additions & 0 deletions app/Console/Commands/Roster/UpdateRoster.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

namespace App\Console\Commands\Roster;

use App\Models\NetworkData\Atc;
use App\Models\Roster;
use Illuminate\Console\Command;
use Illuminate\Support\Carbon;

class UpdateRoster extends Command
{
protected $signature = 'roster:update {fromDate} {toDate}';

protected $description = 'Update the ATC roster based on ATC session data.';

protected int $minimumHours = 3;

protected Carbon $fromDate;

protected Carbon $toDate;

public function handle()
{
$this->fromDate = Carbon::parse($this->argument('fromDate'))->startOfDay();
$this->toDate = Carbon::parse($this->argument('toDate'))->endOfDay();

$eligible = Atc::with(['account.states'])
->select(['networkdata_atc.account_id'])
->whereBetween('disconnected_at', [$this->fromDate, $this->toDate])
->accountIsPartOfUk()
->positionIsWithinUk()
->groupBy('account_id')
->havingRaw("SUM(minutes_online) / 60 > {$this->minimumHours}")
->pluck('account_id');

// On the roster, do not need to be on...
Roster::withoutGlobalScopes()
->whereNotIn('account_id', $eligible)
->get()
->each
->remove();

// Not on the roster, need to be on...
Roster::upsert(
$eligible->map(fn ($value) => ['account_id' => $value])->toArray(),
['account_id']
);

$this->comment('✅ Roster updated!');
}
}
48 changes: 48 additions & 0 deletions app/Console/Commands/Roster/UpdateRosterGanderControllers.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

namespace App\Console\Commands\Roster;

use App\Models\Atc\PositionGroup;
use App\Models\Mship\Account\Endorsement;
use App\Models\Roster;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Http;

class UpdateRosterGanderControllers extends Command
{
protected $signature = 'roster:gander';

protected $description = 'Update the ATC roster based on those within the Gander roster.';

public function handle()
{
$gander = Http::get('https://ganderoceanic.ca/api/roster')
->collect()
->where('active', true)
->pluck('cid');

DB::transaction(function () use ($gander) {
Roster::upsert(
$gander->map(fn ($value) => ['account_id' => $value])->toArray(),
['account_id']
);

$positionGroup = PositionGroup::where('name', 'Shanwick Oceanic (EGGX)')->firstOrFail();

$gander->reject(function ($value) use ($positionGroup) {
return Endorsement::where([
'account_id' => $value,
'endorsable_id' => $positionGroup->id,
'endorsable_type' => PositionGroup::class,
])->exists();
})->each(function ($value) use ($positionGroup) {
Endorsement::create([
'account_id' => $value,
'endorsable_id' => $positionGroup->id,
'endorsable_type' => PositionGroup::class,
]);
});
});
}
}
4 changes: 4 additions & 0 deletions app/Console/Kernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ protected function schedule(Schedule $schedule)
->hourlyAt(15)
->graceTimeInMinutes(15);

$schedule->command('roster:gander')
->hourlyAt(25)
->graceTimeInMinutes(5);

// === By Day === //

$schedule->command('telescope:prune')
Expand Down
29 changes: 0 additions & 29 deletions app/Events/Training/AccountChangedStatusInWaitingList.php

This file was deleted.

36 changes: 36 additions & 0 deletions app/Events/Training/EndorsementRequestApproved.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

namespace App\Events\Training;

use App\Models\Mship\Account\EndorsementRequest;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class EndorsementRequestApproved
{
use Dispatchable, InteractsWithSockets, SerializesModels;

/**
* Create a new event instance.
*/
public function __construct(
private EndorsementRequest $endorsementRequest,
private ?int $days
) {
}

public function getEndorsementRequest(): EndorsementRequest
{
return $this->endorsementRequest;
}

public function getExpiryDate()
{
if ($this->days === null) {
return null;
}

return now()->addDays($this->days)->endOfDay();
}
}
3 changes: 3 additions & 0 deletions app/Filament/Resources/AccountResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use App\Filament\Resources\AccountResource\Pages;
use App\Filament\Resources\AccountResource\RelationManagers;
use App\Models\Mship\Account;
use App\Models\Roster;
use AxonC\FilamentCopyablePlaceholder\Forms\Components\CopyablePlaceholder;
use Carbon\CarbonInterface;
use Filament\Forms;
Expand Down Expand Up @@ -75,6 +76,7 @@ public static function form(Form $form): Form

Forms\Components\Placeholder::make('has_secondary_password')->content(fn ($record) => $record->hasPassword() ? 'Yes' : 'No'),
Forms\Components\Placeholder::make('discord_id')->label('Discord ID')->content(fn ($record) => $record->discord_id ?? new HtmlString('<i>Not Linked</i>')),
Forms\Components\Placeholder::make('roster_status')->label('Roster Status')->content(fn ($record) => Roster::where('account_id', $record->id)->exists() ? 'Active' : 'Inactive'),

Forms\Components\Fieldset::make('Emails')->schema([
Forms\Components\TextInput::make('email')
Expand Down Expand Up @@ -151,6 +153,7 @@ public static function getRelations(): array
RelationManagers\RolesRelationManager::class,
RelationManagers\BansRelationManager::class,
RelationManagers\NotesRelationManager::class,
RelationManagers\EndorsementsRelationManager::class,
];
}

Expand Down
20 changes: 20 additions & 0 deletions app/Filament/Resources/AccountResource/Pages/ViewAccount.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use App\Filament\Resources\AccountResource;
use App\Jobs\UpdateMember;
use App\Models\Contact;
use App\Models\Roster;
use App\Notifications\Mship\UserImpersonated;
use Filament\Actions;
use Filament\Actions\ActionGroup;
Expand All @@ -30,6 +31,8 @@ protected function getLogActionName(): string

protected function getHeaderActions(): array
{
$onRoster = Roster::where('account_id', $this->record->id)->exists();

return [

Actions\Action::make('request_central_update')
Expand All @@ -43,6 +46,23 @@ protected function getHeaderActions(): array

ActionGroup::make([
$this->getImpersonateAction(),
Actions\Action::make('toggle_roster_status')
->visible(fn () => auth()->user()->can('roster.manage'))
->color($onRoster ? 'danger' : 'success')
->icon('heroicon-o-key')
->name($onRoster ? 'Remove from roster' : 'Add to roster')
->modalHeading($onRoster ? 'Remove from roster' : 'Add to roster')
->action(function () use ($onRoster) {
Roster::withoutGlobalScopes()->where('account_id', $this->record->id)->delete();

if (! $onRoster) {
Roster::create(['account_id' => $this->record->id]);
}

$this->refreshFormData(['roster_status']);
})
->requiresConfirmation()
->successNotificationTitle('Roster status updated!'),
Actions\Action::make('remove_password')
->visible(fn () => $this->record->hasPassword() && auth()->user()->can('removeSecondaryPassword', $this->record))
->color('warning')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<?php

namespace App\Filament\Resources\AccountResource\RelationManagers;

use App\Models\Atc\PositionGroup;
use App\Models\Mship\Account\Endorsement;
use Filament\Forms;
use Filament\Forms\Form;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Tables;
use Filament\Tables\Table;

class EndorsementsRelationManager extends RelationManager
{
protected static string $relationship = 'endorsements';

protected static ?string $inverseRelationship = 'account';

protected static ?string $recordTitleAttribute = 'endorsable.name';

public function form(Form $form): Form
{
return $form
->schema([
Forms\Components\Select::make('position_group_id')
->label('Endorsement')
->required()
->options(PositionGroup::unassignedFor($this->ownerRecord)->mapWithKeys(function (PositionGroup $model) {
return [$model->getKey() => str($model->name)];
}))
->hiddenOn('edit'),

// TODO: determine maximum time in advance.
Forms\Components\DatePicker::make('expires_at')
->native(false)
->label('Expires')
->minDate(now()),

Forms\Components\Hidden::make('created_by')
->afterStateHydrated(fn ($component, $state) => $component->state(auth()->user()->getKey())),
]);
}

public function isReadOnly(): bool
{
return false;
}

public function table(Table $table): Table
{
return $table
->recordTitle(fn ($record) => "{$record->endorsable->name} endorsement")
->columns([
Tables\Columns\TextColumn::make('endorsable.name')->label('Name'),
// TODO: color on type
Tables\Columns\TextColumn::make('type')->label('Type')->badge(),
Tables\Columns\TextColumn::make('created_at')->label('Granted')->date(),
Tables\Columns\TextColumn::make('expires_at')->label('Expires')->date()->default(''),
])
->headerActions([
// Tables\Actions\CreateAction::make()->label('Add endorsement'),
])
->actions([
// TODO: define permissions for both actions
// Tables\Actions\EditAction::make()
// ->visible(fn ($record) => $record->type == 'Temporary'),
// Tables\Actions\DeleteAction::make()
// ->visible(fn ($record) => $record->type == 'Permanent'),
]);
}

public static function createEndorsement(array $data, self $livewire)
{
$creator = auth()->user();

Endorsement::create([
'account_id' => $livewire->ownerRecord->id,
'position_group_id' => $data['position_group_id'],
'expires_at' => $data['expires_at'],
'created_by' => $creator->id,
]);
}
}
Loading

0 comments on commit 5a9fea9

Please sign in to comment.