table = $db->prefix . 'us_group_enrollments'; } public function insert( Enrollment $enrollment ): int { $this->db->insert( $this->table, [ 'offering_id' => $enrollment->offeringId, 'student_id' => $enrollment->studentId, 'instructor_id' => $enrollment->instructorId, 'status' => $enrollment->status, 'payment_id' => $enrollment->paymentId, 'enrolled_at' => current_time( 'mysql' ), ], [ '%d', '%d', '%d', '%s', '%d', '%s' ] ); return $this->db->insert_id; } public function findById( int $id ): ?Enrollment { $row = $this->db->get_row( $this->db->prepare( 'SELECT * FROM %i WHERE id = %d', $this->table, $id ) ); return $row ? Enrollment::fromRow( $row ) : null; } /** * Count active enrolments for an offering (capacity check). */ public function countActiveForOffering( int $offeringId ): int { return (int) $this->db->get_var( $this->db->prepare( 'SELECT COUNT(*) FROM %i WHERE offering_id = %d AND status = %s', $this->table, $offeringId, Enrollment::STATUS_ACTIVE ) ); } /** * Count a student's active group-class enrolments. */ public function countActiveForStudent( int $studentId ): int { return (int) $this->db->get_var( $this->db->prepare( 'SELECT COUNT(*) FROM %i WHERE student_id = %d AND status = %s', $this->table, $studentId, Enrollment::STATUS_ACTIVE ) ); } /** * Whether a student already holds an active enrolment in an offering. */ public function hasActiveEnrollment( int $offeringId, int $studentId ): bool { $count = (int) $this->db->get_var( $this->db->prepare( 'SELECT COUNT(*) FROM %i WHERE offering_id = %d AND student_id = %d AND status = %s', $this->table, $offeringId, $studentId, Enrollment::STATUS_ACTIVE ) ); return $count > 0; } /** * A student's enrolments, newest first. * * @return list */ public function findByStudent( int $studentId ): array { $rows = $this->db->get_results( $this->db->prepare( 'SELECT * FROM %i WHERE student_id = %d ORDER BY enrolled_at DESC', $this->table, $studentId ) ); return array_map( Enrollment::fromRow( ... ), $rows ?? [] ); } /** * Enrolments in an instructor's group classes, newest first. * * @return list */ public function findByInstructor( int $instructorId ): array { $rows = $this->db->get_results( $this->db->prepare( 'SELECT * FROM %i WHERE instructor_id = %d ORDER BY enrolled_at DESC', $this->table, $instructorId ) ); return array_map( Enrollment::fromRow( ... ), $rows ?? [] ); } /** * All active enrolments across instructors (studio-admin view). * * @return list */ public function findAllActive(): array { $rows = $this->db->get_results( $this->db->prepare( 'SELECT * FROM %i WHERE status = %s ORDER BY enrolled_at DESC', $this->table, Enrollment::STATUS_ACTIVE ) ); return array_map( Enrollment::fromRow( ... ), $rows ?? [] ); } /** * Active enrolments whose group class bills on a scheduled mode (weekly / * monthly) — the source rows for the daily billing scan. Filtered by joining * the offering so only classes actually on a scheduled plan are returned. * * @param list $modes Billing modes to include (e.g. weekly, monthly). * @return list */ public function findActiveByBillingModes( array $modes ): array { if ( [] === $modes ) { return []; } $offTable = str_replace( 'us_group_enrollments', 'us_offerings', $this->table ); $placeholders = implode( ', ', array_fill( 0, count( $modes ), '%s' ) ); $rows = $this->db->get_results( $this->db->prepare( "SELECT e.* FROM %i e JOIN %i o ON o.id = e.offering_id WHERE e.status = %s AND o.billing_mode IN ( {$placeholders} ) ORDER BY e.student_id ASC, e.offering_id ASC", $this->table, $offTable, Enrollment::STATUS_ACTIVE, ...$modes ) ); return array_map( Enrollment::fromRow( ... ), $rows ?? [] ); } public function setPaymentId( int $id, int $paymentId ): bool { return false !== $this->db->update( $this->table, [ 'payment_id' => $paymentId ], [ 'id' => $id ], [ '%d' ], [ '%d' ] ); } public function updateStatus( int $id, string $status ): bool { if ( ! in_array( $status, Enrollment::VALID_STATUSES, true ) ) { return false; } return (bool) $this->db->update( $this->table, [ 'status' => $status ], [ 'id' => $id ], [ '%s' ], [ '%d' ] ); } }