read = new ResourceCapacityRepository(); } public function present(): bool { global $wpdb; $version = get_option( 'abientot_groups_schema_version', '' ); $v2 = $version === '2';$present = OptionalStoreSchema::present( $this->read, 'abientot_groups_schema_version', $v2 ? '2' : '1', array( 'group_sessions' => array( 'id' => 'bigint unsigned', 'service_id' => 'bigint unsigned', 'staff_id' => 'bigint unsigned', 'location_id' => 'bigint unsigned', 'start_utc' => 'datetime', 'end_utc' => 'datetime', 'busy_start_utc' => 'datetime', 'busy_end_utc' => 'datetime', 'duration_minutes' => 'int unsigned', 'capacity' => 'int unsigned', 'price_cents' => 'bigint unsigned', 'currency' => 'char(3)', 'status' => 'varchar(20)' ), 'group_memberships' => array_merge( array( 'session_id' => 'bigint unsigned', 'appointment_id' => 'bigint unsigned' ), $v2 ? array( 'seat_count' => 'int unsigned', 'unit_price_cents' => 'bigint unsigned' ) : array() ), 'group_commands' => array( 'id' => 'bigint unsigned', 'submission_uuid' => 'char(36)', 'command_hash' => 'char(64)', 'state' => 'varchar(20)', 'appointment_id' => 'bigint unsigned', 'session_id' => 'bigint unsigned' ) ), array( 'group_sessions' => array( array( 'id' ) ), 'group_memberships' => array( array( 'session_id', 'appointment_id' ), array( 'appointment_id' ) ), 'group_commands' => array( array( 'id' ), array( 'submission_uuid' ) ) ) ); if ( ! $present && $this->read->exists( 'resource_allocations' ) ) { $rows = Db::rows( $wpdb->suppress_errors( true ), $wpdb->get_results( "SELECT owner_id FROM {$wpdb->prefix}abientot_resource_allocations WHERE owner_type='session' LIMIT 1", ARRAY_A ), 'retained_capacity_storage_unavailable' ); if ( $rows ) { throw new \RuntimeException( 'retained_group_owner_missing' ); } } return $present; } public function membership( int $appointment ): ?array { global $wpdb; if ( ! $this->present() ) { return null; } if ( get_option( 'abientot_groups_schema_version' ) === '2' ) { $rows = Db::rows( $wpdb->suppress_errors( true ), $wpdb->get_results( $wpdb->prepare( "SELECT m.session_id,m.seat_count,m.unit_price_cents FROM {$wpdb->prefix}abientot_group_memberships m WHERE m.appointment_id=%d", $appointment ), ARRAY_A ), 'retained_capacity_storage_unavailable' ); } else { $rows = Db::rows( $wpdb->suppress_errors( true ), $wpdb->get_results( $wpdb->prepare( "SELECT m.session_id,1 AS seat_count FROM {$wpdb->prefix}abientot_group_memberships m WHERE m.appointment_id=%d", $appointment ), ARRAY_A ), 'retained_capacity_storage_unavailable' ); } return $rows[0] ?? null; } public function blockers_for_staff( int $staff, string $start, string $end, ?int $excludeSession = null ): array { global $wpdb; if ( ! $this->present() ) { return array(); } $excluded = $excludeSession ? (int) $excludeSession : 0; $rows = Db::rows( $wpdb->suppress_errors( true ), $wpdb->get_results( $wpdb->prepare( "SELECT id,service_id,staff_id,location_id,start_utc,end_utc,timezone,duration_minutes,busy_start_utc AS blocked_start_utc,busy_end_utc AS blocked_end_utc FROM {$wpdb->prefix}abientot_group_sessions WHERE staff_id=%d AND busy_start_utc<%s AND busy_end_utc>%s AND status<>%s AND ( %d = 0 OR id<>%d ) ORDER BY start_utc LIMIT 10001", $staff, $end, $start, 'cancelled', $excluded, $excluded ), ARRAY_A ), 'retained_capacity_storage_unavailable' ); if ( count( $rows ) > 10000 ) { throw new \RuntimeException( 'group_capacity_limit' ); } foreach ( $rows as&$row ) { $row['source'] = 'group_session'; $row['session_id'] = (int) $row['id']; $row['id'] = - (int) $row['id']; } unset( $row ); return $rows; } }