Created
          September 30, 2025 13:46 
        
      - 
      
- 
        Save fredbradley/33cb9fec939c009643122a94b09f368e to your computer and use it in GitHub Desktop. 
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
  | <?php | |
| namespace App\Services; | |
| use AllowDynamicProperties; | |
| use App\Exceptions\UnableToFindRegistrationDataInTheFuture; | |
| use App\Exceptions\UnknownRegistrationType; | |
| use App\Logic\ConductAndRewards\CreditsAndDemerits\Credit; | |
| use App\Models\House; | |
| use App\Models\Pupil; | |
| use App\Models\School; | |
| use Carbon\CarbonInterface; | |
| use Exception; | |
| use GuzzleHttp\Exception\ClientException; | |
| use GuzzleHttp\Exception\ConnectException; | |
| use GuzzleHttp\Exception\GuzzleException; | |
| use GuzzleHttp\Exception\ServerException; | |
| use Illuminate\Support\Collection; | |
| use Illuminate\Support\Facades\Log; | |
| use Illuminate\Support\ItemNotFoundException; | |
| use spkm\isams\Controllers\RoughAndReadyController; | |
| use spkm\isams\Exceptions\IsamsInstanceNotFound; | |
| use stdClass; | |
| #[AllowDynamicProperties] class IsamsRegistrationService | |
| { | |
| public Collection $absenceCodes; | |
| public Collection $presentCodes; | |
| public ?object $registrationPeriods = null; | |
| private RoughAndReadyController $controller; | |
| /** | |
| * @throws GuzzleException | |
| * @throws IsamsInstanceNotFound | |
| */ | |
| public function __construct() | |
| { | |
| $this->controller = new RoughAndReadyController; | |
| $this->setPresentCodes(); | |
| $this->setAbsenceCodes(); | |
| } | |
| /** | |
| * @throws GuzzleException | |
| * @throws IsamsInstanceNotFound | |
| */ | |
| public function setPresentCodes(): void | |
| { | |
| try { | |
| $this->presentCodes = collect($this->controller->get('registration/presentcodes')->presentCodes); | |
| } catch (ClientException $exception) { | |
| if ($exception->getCode() === 404) { | |
| throw new IsamsInstanceNotFound( | |
| 'ISAMS gave us a 404 when trying to get present codes.', | |
| 404, | |
| $exception | |
| ); | |
| } | |
| session()->flash('alert-danger', 'Could not retrieve data from ISAMS. Check connection.'); | |
| } catch (ConnectException $exception) { | |
| session()->flash('alert-danger', 'Could not connect to ISAMS. Check connection.'); | |
| $this->presentCodes = collect([]); | |
| } | |
| } | |
| /** | |
| * @throws GuzzleException | |
| */ | |
| public function setAbsenceCodes(): void | |
| { | |
| try { | |
| $this->absenceCodes = collect($this->controller->get('registration/absencecodes')->absenceCodes); | |
| } catch (ClientException $exception) { | |
| session()->flash('alert-danger', 'Could not retrieve data from ISAMS. Check connection.'); | |
| } catch (ConnectException $exception) { | |
| session()->flash('alert-danger', 'Could not connect to ISAMS. Check connection.'); | |
| $this->absenceCodes = collect([]); | |
| } | |
| } | |
| /** | |
| * @deprecated This method is deprecated and will be removed in a future version. | |
| * | |
| * We no longer need to define a school to use this service | |
| */ | |
| public function withSchool(School $school): IsamsRegistrationService | |
| { | |
| return $this; | |
| } | |
| /** | |
| * @throws GuzzleException | |
| */ | |
| public function hasDemeritBeenGiven(House $house, CarbonInterface $date, string $type, Pupil $pupil): bool | |
| { | |
| $result = collect($this->controller->get( | |
| 'rewardsAndConduct/students/'.$pupil->mis_id.'/rewards', [ | |
| // can't figure out how to get $filter to work as per API docs | |
| ] | |
| )->rewards)->filter(function ($reward) { | |
| return $reward->date === now()->format('Y-m-d'); | |
| })->filter(function ($reward) use ($type) { | |
| $str = match ($type) { | |
| 'Am' => 'Late for Morning Callover', | |
| 'Pm' => 'Late for Lunchtime Callover', | |
| default => throw new Exception('Invalid callover type') | |
| }; | |
| return str_contains($reward->description, $str); | |
| }); | |
| return $result->count() > 0; | |
| } | |
| /** | |
| * @throws Exception | |
| * @throws GuzzleException | |
| */ | |
| public function giveLateCalloverHouseDemerit(Pupil $pupil, CarbonInterface $date, string $type) | |
| { | |
| $this->sanitizeType($type); | |
| $credit = new Credit( | |
| $pupil, | |
| auth()->user()->staff, | |
| 'House Demerit' | |
| ); | |
| $description = match ($type) { | |
| 'Am' => 'Late for Morning Callover '.$date->format('d/m/Y'), | |
| 'Pm' => 'Late for Lunchtime Callover '.$date->format('d/m/Y') | |
| }; | |
| $credit | |
| ->setCategory('Late for callover / sport') | |
| ->setDescription($description); | |
| return $credit->post(); | |
| } | |
| /** | |
| * This works like a validator, rather than setting a santized type, it throws an exception if the type is not valid. | |
| * | |
| * @throws UnknownRegistrationType | |
| */ | |
| public function sanitizeType(string $type): void | |
| { | |
| $type = ucfirst($type); | |
| if ($type !== 'Am' && $type !== 'Pm' && $type !== 'Other' && $type !== 'Ev') { | |
| throw new UnknownRegistrationType('Unknown Registration Type. Should be either "Am" or "Pm" or "Ev".'); | |
| } | |
| } | |
| /** | |
| * @return mixed | |
| * | |
| * @throws GuzzleException | |
| */ | |
| public function registerPupil( | |
| string $misId, | |
| int $registrationPeriodId, | |
| bool $isPresent, | |
| int $absenceCodeId | |
| ) { | |
| if ($isPresent) { | |
| $isLate = false; | |
| $data = compact('isPresent', 'isLate'); | |
| } else { | |
| $data = compact('absenceCodeId', 'isPresent'); | |
| } | |
| return $this->updatePupilRegistrationStatus($misId, $registrationPeriodId, $data); | |
| } | |
| /** | |
| * @throws GuzzleException | |
| */ | |
| public function updatePupilRegistrationStatus(string $misId, int $registrationPeriodId, array $data) | |
| { | |
| return $this->controller->request( | |
| 'PUT', | |
| 'registration/register/'.$registrationPeriodId.'/students/'.$misId, [], $data, | |
| ); | |
| } | |
| /** | |
| * @throws GuzzleException | |
| */ | |
| public function getPupilRegistrationStatus(string $misId, int $registrationPeriodId): object | |
| { | |
| try { | |
| return $this->controller->request( | |
| 'GET', | |
| 'registration/register/'.$registrationPeriodId.'/students/'.$misId | |
| ); | |
| } catch (ClientException $exception) { | |
| if ($exception->getCode() === 404) { | |
| session()->flash('alert-warning', 'Registration status does not exist.'); | |
| return (object) []; // TODO: should this return an HTTP 404 exception instead? Think about knock on effects... | |
| } else { | |
| throw $exception; | |
| } | |
| } | |
| } | |
| /** | |
| * @throws UnableToFindRegistrationDataInTheFuture|GuzzleException | |
| */ | |
| public function getCalloverAbsentees(House $house, CarbonInterface $date, string $type): Collection | |
| { | |
| $housePupils = $house->pupils()->current()->get(); | |
| return $this->prepareCalloverData($date, $type) | |
| ->whereIn('schoolId', $housePupils->pluck('mis_id')) | |
| ->map(function ($pupil) use ($housePupils) { | |
| $pupil->absent = optional($this->absenceCodes->where('id', | |
| $pupil->absenceCodeId)->first())->name; | |
| $pupil->present = optional($this->presentCodes->where('id', | |
| $pupil->presentCodeId)->first())->name; | |
| $pupil->name = $housePupils->where('mis_id', $pupil->schoolId)->first()->name; | |
| return $pupil; | |
| })->whereNotNull('absent'); | |
| } | |
| /** | |
| * @throws UnableToFindRegistrationDataInTheFuture | |
| * @throws GuzzleException | |
| * @throws Exception | |
| */ | |
| public function prepareCalloverData(CarbonInterface $date, string $type, ?School $school = null): Collection | |
| { | |
| if (is_null($school)) { | |
| $school = defaultIsamsInstitution(); | |
| } | |
| if ($date->isAfter(now())) { | |
| throw new UnableToFindRegistrationDataInTheFuture('Cannot find registration data for '.$date->format('Y-m-d').' when it is currently '.now()->format('Y-m-d'), | |
| 400); | |
| } | |
| if ($type === 'Latest') { | |
| $type = $date->isToday() ? (now()->hour < 13 ? 'Am' : 'Pm') : 'Pm'; | |
| } | |
| $this->sanitizeType($type); | |
| try { | |
| $callover = $this->getRegistrationPeriod($date, $type, $school); | |
| if (is_null($callover)) { | |
| throw new ItemNotFoundException('Callover Period not found'); | |
| } | |
| } catch (ItemNotFoundException $exception) { | |
| session()->flash('Callover Period not found'); | |
| Log::warning('Callover Period not found'); | |
| return collect(); | |
| } | |
| try { | |
| $result = $this->controller->get('registration/register/'.$callover->id, [ | |
| 'pageSize' => 800, | |
| ]); | |
| } catch (ServerException $exception) { | |
| if (str_contains($exception->getMessage(), | |
| 'Registration Statuses have not been generated for this registration')) { | |
| session()->flash('alert-warning', 'Tried and failed to get registration data that does not yet exist'); | |
| Log::warning('Tried and failed to get registration data that does not yet exist'); | |
| return collect(); | |
| } | |
| throw $exception; | |
| } | |
| return collect($result->registrationStatuses); | |
| } | |
| /** | |
| * @throws Exception | |
| * @throws GuzzleException | |
| */ | |
| public function getRegistrationPeriod(CarbonInterface $date, string $type, ?School $school = null): ?object | |
| { | |
| if ($school === null) { | |
| $school = defaultIsamsInstitution(); | |
| } | |
| if ($type === 'Latest') { | |
| $type = now()->hour < 13 ? 'Am' : 'Pm'; | |
| } | |
| $this->sanitizeType($type); | |
| $periods = $this->getRegistrationPeriods([ | |
| 'startDate' => $date->format('Y-m-d'), | |
| 'endDate' => $date->format('Y-m-d'), | |
| ]); | |
| if (strtolower($type) === 'ev') { | |
| $type = 'Other'; | |
| } | |
| return collect($periods->registrationPeriods) | |
| ->where('registrationType', '=', $type) | |
| ->filter(function (stdClass $callover) use ($school) { | |
| return $callover->divisions[0]->id === $school->division_id; // Senior School | |
| }) | |
| ->first(); | |
| } | |
| /** | |
| * @throws GuzzleException | |
| */ | |
| public function getRegistrationPeriods(array $options = []): mixed | |
| { | |
| try { | |
| $this->registrationPeriods = $this->controller->get('registration/periods', $options); | |
| } catch (ClientException $exception) { | |
| if ($exception->getCode() === 422) { | |
| $this->registrationPeriods = (object) [ | |
| 'registrationPeriods' => [], | |
| ]; | |
| session()->flash('alert-warning', 'No registration periods found for this date.'); | |
| } | |
| } catch (Exception $exception) { | |
| session()->flash('alert-danger', 'Could not retrieve data from ISAMS. Check connection.'); | |
| $this->registrationPeriods = (object) [ | |
| 'registrationPeriods' => [], | |
| ]; | |
| } | |
| return $this->registrationPeriods; | |
| } | |
| public function getPeriodRegistrationStatuses(int $periodId, array $options) | |
| { | |
| try { | |
| return $this->controller->get('registration/register/'.$periodId, $options); | |
| } catch (Exception $exception) { | |
| session()->flash('alert-danger', 'Could not retrieve data from ISAMS. Check connection.'); | |
| return (object) [ | |
| 'registrationStatuses' => [], | |
| ]; | |
| } | |
| } | |
| } | 
  
    Sign up for free
    to join this conversation on GitHub.
    Already have an account?
    Sign in to comment