* * @throws DomainException When not found. */ public function handle( int $id ): array { $customer = $this->customers->find_by_id( $id ); if ( null === $customer || $customer->is_deleted() ) { throw new DomainException( 'customer.not_found', 'Customer not found.' ); } $history = $this->bookings->find_history_for_customer( $id ); $payload = $this->to_admin_array( $customer ); $first = $this->earliest_start( $history ); $payload['bookings_count'] = count( $history ); $payload['first_booking_at'] = null === $first ? null : $first->format( 'c' ); $payload['bookings'] = $this->map_bookings( $history ); return $payload; } /** * Map a customer entity to the admin API shape. * * @param Customer $customer Customer entity. * @return array */ private function to_admin_array( Customer $customer ): array { $display = trim( $customer->first_name() . ' ' . ( $customer->last_name() ?? '' ) ); return [ 'id' => $customer->id(), 'uuid' => $customer->uuid()->to_string(), 'display_name' => '' !== $display ? $display : $customer->first_name(), 'email' => $customer->email()->to_string(), 'phone' => $customer->phone()?->to_string(), 'first_name' => $customer->first_name(), 'last_name' => $customer->last_name(), 'status' => $customer->status()->value, 'timezone' => $customer->timezone()?->to_string(), 'locale' => $customer->locale(), 'notes' => $customer->notes(), 'marketing_consent' => $customer->marketing_consent(), 'created_at' => $customer->created_at()->format( 'c' ), ]; } /** * Map bookings for the drawer and detail screen. * * @param list $bookings History, newest first. * @return list> */ private function map_bookings( array $bookings ): array { $names = []; $out = []; foreach ( $bookings as $booking ) { $id = $booking->id(); if ( null === $id ) { continue; } $service_id = $booking->service_id(); if ( ! array_key_exists( $service_id, $names ) ) { $service = $this->services->find_by_id( $service_id ); $names[ $service_id ] = null !== $service ? $service->name() : ''; } $out[] = [ 'id' => $id, 'service_name' => $names[ $service_id ], 'start_at' => $booking->start_utc()->format( 'c' ), 'status' => $booking->status()->value, 'total_minor' => $booking->price()->amount_minor(), ]; } return $out; } /** * Earliest start among history rows. * * @param list $bookings History. */ private function earliest_start( array $bookings ): ?DateTimeImmutable { $first = null; foreach ( $bookings as $booking ) { $start = $booking->start_utc(); if ( null === $first || $start < $first ) { $first = $start; } } return $first; } }