*/ public function policyAcceptances( int $studentId ): array { return array_map( function ( PolicyAcceptance $acceptance ): array { $version = $this->policyVersions->findById( $acceptance->policyVersionId ); $policy = $version ? $this->policies->findById( $version->policyId ) : null; return [ 'policy' => $policy ? $policy->title : sprintf( '#%d', $acceptance->policyVersionId ), 'version' => $version ? sprintf( 'v%d', $version->versionNumber ) : '—', 'context' => $this->contextLabel( $acceptance->registrationType, $acceptance->registrationId ), 'accepted_at' => $acceptance->acceptedAt ?? '', ]; }, $this->acceptances->findByStudent( $studentId ) ); } /** * Booking/enrolment intake answers the student has submitted, newest first. * Account-signup answers are excluded — those are shown on their own under * {@see registrationInfo()}. * * @return list */ public function intakeAnswers( int $studentId ): array { $bookingAnswers = array_filter( $this->answers->findByStudent( $studentId ), static fn( Answer $answer ): bool => Answer::REG_ACCOUNT !== $answer->registrationType ); return array_values( array_map( function ( Answer $answer ): array { $question = $this->questions->findById( $answer->questionId ); return [ 'question' => $question ? $question->label : sprintf( '#%d', $answer->questionId ), 'answer' => $answer->answerValue ?? '—', 'context' => $this->contextLabel( $answer->registrationType, $answer->registrationId ), ]; }, $bookingAnswers ) ); } /** * The student's answers to the studio-wide account-signup questions: every * configured account question paired with the student's answer ("—" when * unanswered, e.g. a question added after they registered). * * @return list */ public function registrationInfo( int $studentId ): array { $byQuestion = []; foreach ( $this->answers->findByRegistration( Answer::REG_ACCOUNT, $studentId ) as $answer ) { $byQuestion[ $answer->questionId ] = $answer->answerValue ?? ''; } return array_map( static function ( Question $question ) use ( $byQuestion ): array { $value = $byQuestion[ (int) $question->id ] ?? ''; return [ 'question' => $question->label, 'answer' => '' === $value ? '—' : $value, 'required' => $question->isRequired, ]; }, $this->questions->findByScope( Question::SCOPE_ACCOUNT ) ); } /** * Every payment for the student, newest first. * * @return list */ public function payments( int $studentId ): array { return array_map( fn( Payment $payment ): array => [ 'created_at' => $payment->createdAt ?? '', 'context' => $this->contextLabel( $payment->registrationType, $payment->registrationId ), 'method' => $payment->method, 'status' => $payment->status, 'amount' => $payment->amount, 'tax_amount' => $payment->taxAmount, 'total' => $payment->total(), 'currency' => $payment->currency, 'receipt' => $payment->receiptNumber ?? '—', ], $this->payments->findByStudent( $studentId ) ); } /** * The student's total unused credit balance (from cancelled paid lessons), * applied automatically against future scheduled-billing charges. */ public function creditBalance( int $studentId ): float { return $this->credits->availableBalance( $studentId ); } /** * Every credit the student has been issued, newest first, with the amount, what * remains, and its state. * * @return list */ public function credits( int $studentId ): array { return array_map( static fn( Credit $credit ): array => [ 'created_at' => $credit->createdAt ?? '', 'amount' => $credit->amount, 'remaining' => $credit->remaining, 'currency' => $credit->currency, 'reason' => $credit->reason ?? '—', 'status' => $credit->status, ], $this->credits->findByStudent( $studentId ) ); } /** * Human label for a polymorphic registration target. */ private function contextLabel( string $registrationType, int $registrationId ): string { switch ( $registrationType ) { case PolicyAcceptance::REG_ACCOUNT: return __( 'Account signup', 'unsupervised-schedular' ); case PolicyAcceptance::REG_LESSON: /* translators: %d: the lesson id */ return sprintf( __( 'Lesson #%d', 'unsupervised-schedular' ), $registrationId ); case PolicyAcceptance::REG_ENROLLMENT: /* translators: %d: the group-class enrolment id */ return sprintf( __( 'Enrolment #%d', 'unsupervised-schedular' ), $registrationId ); default: return sprintf( '%s #%d', $registrationType, $registrationId ); } } }