, total: int} */ public function list_for_service( int $service_id ): array { $durations = $this->durations->find_by_service_id( $service_id ); $items = []; foreach ( $durations as $d ) { $items[] = $this->to_array( $d ); } return [ 'items' => $items, 'total' => count( $items ) ]; } /** * Create a new duration for a service. * * @param array $data Duration data. * @return ServiceDuration * @throws DomainException */ public function create( array $data ): ServiceDuration { $service_id = (int) ( $data['service_id'] ?? 0 ); if ( $service_id < 1 ) { throw new DomainException( 'duration.service_id', 'service_id is required.' ); } $service = $this->services->find_by_id( $service_id ); if ( null === $service ) { throw new DomainException( 'duration.service_not_found', 'Service not found.' ); } $currency = strtoupper( trim( (string) ( $data['currency'] ?? 'GBP' ) ) ); if ( 1 !== preg_match( '/^[A-Z]{3}$/', $currency ) ) { throw new DomainException( 'duration.currency', 'Currency must be a 3-letter ISO 4217 code.' ); } $duration = new ServiceDuration( null, $service_id, (int) ( $data['duration_min'] ?? 0 ), new Money( (int) ( $data['price_minor'] ?? 0 ), $currency ), isset( $data['label'] ) ? trim( (string) $data['label'] ) : null, (bool) ( $data['is_default'] ?? false ), (int) ( $data['sort_order'] ?? 0 ), ); $this->durations->save( $duration ); return $duration; } /** * Update an existing duration. * * @param int $id Duration id. * @param array $data Duration data. * @return ServiceDuration * @throws DomainException */ public function update( int $id, array $data ): ServiceDuration { $duration = $this->durations->find_by_id( $id ); if ( null === $duration ) { throw new DomainException( 'duration.not_found', 'Duration not found.' ); } $currency = isset( $data['currency'] ) ? strtoupper( trim( (string) $data['currency'] ) ) : $duration->price()->currency(); if ( 1 !== preg_match( '/^[A-Z]{3}$/', $currency ) ) { throw new DomainException( 'duration.currency', 'Currency must be a 3-letter ISO 4217 code.' ); } $new_duration = new ServiceDuration( $duration->id(), $duration->service_id(), array_key_exists( 'duration_min', $data ) ? (int) $data['duration_min'] : $duration->duration_min(), new Money( array_key_exists( 'price_minor', $data ) ? (int) $data['price_minor'] : $duration->price()->amount_minor(), $currency ), array_key_exists( 'label', $data ) ? trim( (string) $data['label'] ) : $duration->label(), array_key_exists( 'is_default', $data ) ? (bool) $data['is_default'] : $duration->is_default(), array_key_exists( 'sort_order', $data ) ? (int) $data['sort_order'] : $duration->sort_order(), ); $this->durations->save( $new_duration ); return $new_duration; } /** * Delete a duration. * * @param int $id Duration id. * @throws DomainException */ public function delete( int $id ): void { $duration = $this->durations->find_by_id( $id ); if ( null === $duration ) { throw new DomainException( 'duration.not_found', 'Duration not found.' ); } $this->durations->delete( $duration ); } /** * Map a duration entity to an admin API array. * * @param ServiceDuration $duration Duration entity. * @return array */ private function to_array( ServiceDuration $duration ): array { return [ 'id' => $duration->id(), 'service_id' => $duration->service_id(), 'duration_min' => $duration->duration_min(), 'price_minor' => $duration->price()->amount_minor(), 'currency' => $duration->price()->currency(), 'label' => $duration->label(), 'is_default' => $duration->is_default(), 'sort_order' => $duration->sort_order(), ]; } }