diff --git a/app/Http/Controllers/Apis/Protected/Summit/OAuth2SummitOrdersApiController.php b/app/Http/Controllers/Apis/Protected/Summit/OAuth2SummitOrdersApiController.php index 403d557c0..9427d6175 100644 --- a/app/Http/Controllers/Apis/Protected/Summit/OAuth2SummitOrdersApiController.php +++ b/app/Http/Controllers/Apis/Protected/Summit/OAuth2SummitOrdersApiController.php @@ -1,4 +1,7 @@ - 'Read All Summit Data', + SummitScopes::ReadRegistrationOrders => 'Read Registration Orders', + SummitScopes::ReadMyRegistrationOrders => 'Read My Registration Orders', + SummitScopes::WriteSummitData => 'Write Summit Data', + SummitScopes::CreateRegistrationOrders => 'Create Registration Orders', + SummitScopes::CreateOfflineRegistrationOrders => 'Create Offline Registration Orders', + SummitScopes::DeleteRegistrationOrders => 'Delete Registration Orders', + SummitScopes::UpdateRegistrationOrders => 'Update Registration Orders', + SummitScopes::UpdateMyRegistrationOrders => 'Update My Registration Orders', + SummitScopes::DeleteMyRegistrationOrders => 'Delete My Registration Orders', + ], + ), + ], +) +] +class OAuth2SummitOrdersApiControllerAuthSchema +{ +} /** * Class OAuth2SummitOrdersApiController * @package App\Http\Controllers */ -final class OAuth2SummitOrdersApiController - extends OAuth2ProtectedController +final class OAuth2SummitOrdersApiController extends OAuth2ProtectedController { use GetSummitChildElementById; @@ -93,14 +126,13 @@ final class OAuth2SummitOrdersApiController */ public function __construct ( - ISummitOrderRepository $repository, - ISummitRepository $summit_repository, + ISummitOrderRepository $repository, + ISummitRepository $summit_repository, ISummitAttendeeTicketRepository $ticket_repository, - ISummitRefundRequestRepository $refund_request_repository, - ISummitOrderService $service, - IResourceServerContext $resource_server_context - ) - { + ISummitRefundRequestRepository $refund_request_repository, + ISummitOrderService $service, + IResourceServerContext $resource_server_context + ) { parent::__construct($resource_server_context); $this->repository = $repository; $this->summit_repository = $summit_repository; @@ -113,11 +145,71 @@ public function __construct * @param $summit_id * @return \Illuminate\Http\JsonResponse|mixed */ + #[OA\Post( + path: '/api/public/v1/summits/{id}/orders/reserve', + summary: 'Reserve tickets in an order (Public)', + description: 'Creates a reservation for tickets. Can be called anonymously or by authenticated users.', + operationId: 'reservePublic', + tags: ['Orders (Public)'], + parameters: [ + new OA\Parameter(name: 'id', in: 'path', required: true, description: 'Summit ID or slug', schema: new OA\Schema(type: 'string')), + ], + requestBody: new OA\RequestBody( + required: true, + content: new OA\JsonContent(ref: '#/components/schemas/ReserveOrderRequest') + ), + responses: [ + new OA\Response( + response: Response::HTTP_CREATED, + description: 'Order reserved successfully', + content: new OA\JsonContent(ref: '#/components/schemas/SummitOrderReservation') + ), + new OA\Response(response: Response::HTTP_BAD_REQUEST, description: 'Bad Request'), + new OA\Response(response: Response::HTTP_NOT_FOUND, description: 'Summit not found'), + new OA\Response(response: Response::HTTP_PRECONDITION_FAILED, description: 'Validation Error'), + ] + )] + /** + * @param $summit_id + * @return \Illuminate\Http\JsonResponse|mixed + */ + #[OA\Post( + path: '/api/v1/summits/{id}/orders/reserve', + summary: 'Reserve tickets in an order', + description: 'Creates a reservation for tickets. Can be called anonymously or by authenticated users.', + operationId: 'reserve', + security: [ + [ + 'summit_orders_auth' => [ + SummitScopes::CreateRegistrationOrders, + ] + ] + ], + tags: ['Orders'], + parameters: [ + new OA\Parameter(name: 'id', in: 'path', required: true, description: 'Summit ID or slug', schema: new OA\Schema(type: 'string')), + ], + requestBody: new OA\RequestBody( + required: true, + content: new OA\JsonContent(ref: '#/components/schemas/ReserveOrderRequest') + ), + responses: [ + new OA\Response( + response: Response::HTTP_CREATED, + description: 'Order reserved successfully', + content: new OA\JsonContent(ref: '#/components/schemas/SummitOrderReservation') + ), + new OA\Response(response: Response::HTTP_BAD_REQUEST, description: 'Bad Request'), + new OA\Response(response: Response::HTTP_NOT_FOUND, description: 'Summit not found'), + new OA\Response(response: Response::HTTP_PRECONDITION_FAILED, description: 'Validation Error'), + ] + )] public function reserve($summit_id) { return $this->processRequest(function () use ($summit_id) { $summit = SummitFinderStrategyFactory::build($this->summit_repository, $this->getResourceServerContext())->find($summit_id); - if (is_null($summit)) return $this->error404(); + if (is_null($summit)) + return $this->error404(); $owner = $this->getResourceServerContext()->getCurrentUser(); @@ -166,13 +258,15 @@ public function reserve($summit_id) ( SerializerRegistry::getInstance()->getSerializer ( - $order, ISummitOrderSerializerTypes::ReservationType + $order, + ISummitOrderSerializerTypes::ReservationType )->serialize - ( - SerializerUtils::getExpand(), - SerializerUtils::getFields(), - SerializerUtils::getRelations() - )); + ( + SerializerUtils::getExpand(), + SerializerUtils::getFields(), + SerializerUtils::getRelations() + ) + ); }); } @@ -181,13 +275,39 @@ public function reserve($summit_id) * @param $hash * @return \Illuminate\Http\JsonResponse|mixed */ + #[OA\Put( + path: '/api/public/v1/summits/{id}/orders/{hash}/checkout', + summary: 'Checkout a reserved order', + description: 'Processes payment and completes an order reservation', + operationId: 'checkout', + tags: ['Orders (Public)'], + parameters: [ + new OA\Parameter(name: 'id', in: 'path', required: true, description: 'Summit ID or slug', schema: new OA\Schema(type: 'string')), + new OA\Parameter(name: 'hash', in: 'path', required: true, description: 'Order hash', schema: new OA\Schema(type: 'string')), + ], + requestBody: new OA\RequestBody( + required: false, + content: new OA\JsonContent(ref: '#/components/schemas/CheckoutOrderRequest') + ), + responses: [ + new OA\Response( + response: Response::HTTP_CREATED, + description: 'Order checked out successfully', + content: new OA\JsonContent(ref: '#/components/schemas/SummitOrderCheckout') + ), + new OA\Response(response: Response::HTTP_BAD_REQUEST, description: 'Bad Request'), + new OA\Response(response: Response::HTTP_NOT_FOUND, description: 'Summit or order not found'), + new OA\Response(response: Response::HTTP_PRECONDITION_FAILED, description: 'Validation Error'), + ] + )] public function checkout($summit_id, $hash) { return $this->processRequest(function () use ($summit_id, $hash) { $summit = SummitFinderStrategyFactory::build($this->summit_repository, $this->getResourceServerContext())->find($summit_id); - if (is_null($summit)) return $this->error404(); + if (is_null($summit)) + return $this->error404(); $payload = $this->getJsonPayload([ 'billing_address_1' => 'nullable|sometimes|string|max:255', @@ -206,10 +326,10 @@ public function checkout($summit_id, $hash) $order, ISummitOrderSerializerTypes::CheckOutType )->serialize( - SerializerUtils::getExpand(), - SerializerUtils::getFields(), - SerializerUtils::getRelations() - )); + SerializerUtils::getExpand(), + SerializerUtils::getFields(), + SerializerUtils::getRelations() + )); }); } @@ -218,21 +338,42 @@ public function checkout($summit_id, $hash) * @param $hash * @return \Illuminate\Http\JsonResponse|mixed */ + #[OA\Get( + path: '/api/public/v1/summits/{id}/orders/{hash}/tickets/mine', + summary: 'Get my ticket by order hash', + description: 'Returns ticket information for the current user using order hash', + operationId: 'getMyTicketByOrderHash', + tags: ['Orders (Public)'], + parameters: [ + new OA\Parameter(name: 'id', in: 'path', required: true, description: 'Summit ID or slug', schema: new OA\Schema(type: 'string')), + new OA\Parameter(name: 'hash', in: 'path', required: true, description: 'Order hash', schema: new OA\Schema(type: 'string')), + ], + responses: [ + new OA\Response( + response: Response::HTTP_CREATED, + description: 'Ticket information', + content: new OA\JsonContent(ref: '#/components/schemas/SummitAttendeeTicketGuest') + ), + new OA\Response(response: Response::HTTP_NOT_FOUND, description: 'Summit or order not found'), + ] + )] public function getMyTicketByOrderHash($summit_id, $hash) { return $this->processRequest(function () use ($summit_id, $hash) { $summit = SummitFinderStrategyFactory::build($this->summit_repository, $this->getResourceServerContext())->find($summit_id); - if (is_null($summit)) return $this->error404(); + if (is_null($summit)) + return $this->error404(); $ticket = $this->service->getMyTicketByOrderHash($summit, $hash); - return $this->created(SerializerRegistry::getInstance() - ->getSerializer($ticket, ISummitAttendeeTicketSerializerTypes::GuestEdition) - ->serialize( - SerializerUtils::getExpand(), - SerializerUtils::getFields(), - SerializerUtils::getRelations() - ) + return $this->created( + SerializerRegistry::getInstance() + ->getSerializer($ticket, ISummitAttendeeTicketSerializerTypes::GuestEdition) + ->serialize( + SerializerUtils::getExpand(), + SerializerUtils::getFields(), + SerializerUtils::getRelations() + ) ); }); } @@ -242,11 +383,27 @@ public function getMyTicketByOrderHash($summit_id, $hash) * @param $hash * @return \Illuminate\Http\JsonResponse|mixed */ + #[OA\Delete( + path: '/api/public/v1/summits/{id}/orders/{hash}', + summary: 'Cancel order by hash', + description: 'Cancels an order using its hash', + operationId: 'cancel', + tags: ['Orders (Public)'], + parameters: [ + new OA\Parameter(name: 'id', in: 'path', required: true, description: 'Summit ID or slug', schema: new OA\Schema(type: 'string')), + new OA\Parameter(name: 'hash', in: 'path', required: true, description: 'Order hash', schema: new OA\Schema(type: 'string')), + ], + responses: [ + new OA\Response(response: Response::HTTP_NO_CONTENT, description: 'Order cancelled successfully'), + new OA\Response(response: Response::HTTP_NOT_FOUND, description: 'Summit or order not found'), + ] + )] public function cancel($summit_id, $hash) { return $this->processRequest(function () use ($summit_id, $hash) { $summit = SummitFinderStrategyFactory::build($this->summit_repository, $this->getResourceServerContext())->find($summit_id); - if (is_null($summit)) return $this->error404(); + if (is_null($summit)) + return $this->error404(); $this->service->cancel($summit, $hash); return $this->deleted(); }); @@ -256,25 +413,66 @@ public function cancel($summit_id, $hash) * @param $summit_id * @return \Illuminate\Http\JsonResponse|mixed */ + #[OA\Get( + path: '/api/v1/summits/{id}/orders', + summary: 'Get all orders for a summit', + description: 'Returns paginated list of orders for the specified summit. Admin access required.', + operationId: 'getAllBySummit', + security: [ + [ + 'summit_orders_auth' => [ + SummitScopes::ReadAllSummitData, + SummitScopes::ReadRegistrationOrders, + ] + ] + ], + x: [ + 'authz_groups' => [ + IGroup::SuperAdmins, + IGroup::Administrators, + IGroup::SummitAdministrators, + IGroup::SummitRegistrationAdmins, + ] + ], + tags: ['Orders'], + parameters: [ + new OA\Parameter(name: 'id', in: 'path', required: true, description: 'Summit ID or slug', schema: new OA\Schema(type: 'string')), + new OA\Parameter(name: 'page', in: 'query', required: false, description: 'Page number', schema: new OA\Schema(type: 'integer', default: 1)), + new OA\Parameter(name: 'per_page', in: 'query', required: false, description: 'Items per page', schema: new OA\Schema(type: 'integer', default: 10)), + new OA\Parameter(name: 'filter', in: 'query', required: false, description: 'Filter criteria (number, owner_name, owner_email, status, etc.)', schema: new OA\Schema(type: 'string')), + new OA\Parameter(name: 'order', in: 'query', required: false, description: 'Sort order', schema: new OA\Schema(type: 'string')), + ], + responses: [ + new OA\Response( + response: Response::HTTP_OK, + description: 'Paginated list of orders', + content: new OA\JsonContent(ref: '#/components/schemas/PaginatedSummitOrdersResponse') + ), + new OA\Response(response: Response::HTTP_UNAUTHORIZED, description: 'Unauthorized'), + new OA\Response(response: Response::HTTP_FORBIDDEN, description: 'Forbidden'), + new OA\Response(response: Response::HTTP_NOT_FOUND, description: 'Summit not found'), + ] + )] public function getAllBySummit($summit_id) { $summit = SummitFinderStrategyFactory::build($this->summit_repository, $this->getResourceServerContext())->find($summit_id); - if (is_null($summit)) return $this->error404(); + if (is_null($summit)) + return $this->error404(); return $this->_getAll( function () { return [ - 'number' => ['=@', '==','@@'], - 'owner_name' => ['=@', '==','@@'], - 'owner_email' => ['=@', '==','@@'], - 'owner_company' => ['=@', '==','@@'], - 'ticket_owner_name' => ['=@', '==','@@'], - 'ticket_owner_email' => ['=@', '==','@@'], - 'ticket_number' => ['=@', '==','@@'], + 'number' => ['=@', '==', '@@'], + 'owner_name' => ['=@', '==', '@@'], + 'owner_email' => ['=@', '==', '@@'], + 'owner_company' => ['=@', '==', '@@'], + 'ticket_owner_name' => ['=@', '==', '@@'], + 'ticket_owner_email' => ['=@', '==', '@@'], + 'ticket_number' => ['=@', '==', '@@'], 'summit_id' => ['=='], 'owner_id' => ['=='], 'status' => ['==', '<>'], - 'created' => ['>', '<', '<=', '>=', '==','[]'], + 'created' => ['>', '<', '<=', '>=', '==', '[]'], 'amount' => ['==', '<>', '>=', '>'], 'payment_method' => ['=='] ]; @@ -326,25 +524,69 @@ function () { * @param $summit_id * @return \Illuminate\Http\JsonResponse|mixed */ + #[OA\Get( + path: '/api/v1/summits/{id}/orders/csv', + summary: 'Export orders to CSV', + description: 'Exports all orders for a summit to CSV format. Admin access required.', + operationId: 'getAllBySummitCSV', + security: [ + [ + 'summit_orders_auth' => [ + SummitScopes::ReadAllSummitData, + SummitScopes::ReadRegistrationOrders, + ] + ] + ], + x: [ + 'authz_groups' => [ + IGroup::SuperAdmins, + IGroup::Administrators, + IGroup::SummitAdministrators, + IGroup::SummitRegistrationAdmins, + ] + ], + tags: ['Orders'], + parameters: [ + new OA\Parameter(name: 'id', in: 'path', required: true, description: 'Summit ID or slug', schema: new OA\Schema(type: 'string')), + new OA\Parameter(name: 'page', in: 'query', required: false, description: 'Page number', schema: new OA\Schema(type: 'integer', default: 1)), + new OA\Parameter(name: 'per_page', in: 'query', required: false, description: 'Items per page', schema: new OA\Schema(type: 'integer', default: 10)), + new OA\Parameter(name: 'filter', in: 'query', required: false, description: 'Filter criteria', schema: new OA\Schema(type: 'string')), + new OA\Parameter(name: 'order', in: 'query', required: false, description: 'Sort order', schema: new OA\Schema(type: 'string')), + ], + responses: [ + new OA\Response( + response: Response::HTTP_OK, + description: 'CSV file', + content: new OA\MediaType( + mediaType: 'text/csv', + schema: new OA\Schema(type: 'string', format: 'binary') + ) + ), + new OA\Response(response: Response::HTTP_UNAUTHORIZED, description: 'Unauthorized'), + new OA\Response(response: Response::HTTP_FORBIDDEN, description: 'Forbidden'), + new OA\Response(response: Response::HTTP_NOT_FOUND, description: 'Summit not found'), + ] + )] public function getAllBySummitCSV($summit_id) { $summit = SummitFinderStrategyFactory::build($this->summit_repository, $this->getResourceServerContext())->find($summit_id); - if (is_null($summit)) return $this->error404(); + if (is_null($summit)) + return $this->error404(); return $this->_getAllCSV( function () { return [ - 'number' => ['=@', '==','@@'], - 'owner_name' => ['=@', '==','@@'], - 'owner_email' => ['=@', '==','@@'], - 'owner_company' => ['=@', '==','@@'], - 'ticket_owner_name' => ['=@', '==','@@'], - 'ticket_owner_email' => ['=@', '==','@@'], - 'ticket_number' => ['=@', '==','@@'], + 'number' => ['=@', '==', '@@'], + 'owner_name' => ['=@', '==', '@@'], + 'owner_email' => ['=@', '==', '@@'], + 'owner_company' => ['=@', '==', '@@'], + 'ticket_owner_name' => ['=@', '==', '@@'], + 'ticket_owner_email' => ['=@', '==', '@@'], + 'ticket_number' => ['=@', '==', '@@'], 'summit_id' => ['=='], 'owner_id' => ['=='], 'status' => ['==', '<>'], - 'created' => ['>', '<', '<=', '>=', '==','[]'], + 'created' => ['>', '<', '<=', '>=', '==', '[]'], 'amount' => ['==', '<>', '>=', '>'], 'payment_method' => ['=='] ]; @@ -403,6 +645,34 @@ function () { * @return mixed */ + #[OA\Get( + path: '/api/v1/orders/me', + summary: 'Get all my orders across all summits', + description: 'Returns paginated list of current user orders across all summits', + operationId: 'getAllMyOrders', + security: [ + [ + 'summit_orders_auth' => [ + SummitScopes::ReadMyRegistrationOrders, + ] + ] + ], + tags: ['Orders'], + parameters: [ + new OA\Parameter(name: 'page', in: 'query', required: false, description: 'Page number', schema: new OA\Schema(type: 'integer', default: 1)), + new OA\Parameter(name: 'per_page', in: 'query', required: false, description: 'Items per page', schema: new OA\Schema(type: 'integer', default: 10)), + new OA\Parameter(name: 'filter', in: 'query', required: false, description: 'Filter criteria', schema: new OA\Schema(type: 'string')), + new OA\Parameter(name: 'order', in: 'query', required: false, description: 'Sort order', schema: new OA\Schema(type: 'string')), + ], + responses: [ + new OA\Response( + response: Response::HTTP_OK, + description: 'Paginated list of my orders', + content: new OA\JsonContent(ref: '#/components/schemas/PaginatedSummitOrdersResponse') + ), + new OA\Response(response: Response::HTTP_UNAUTHORIZED, description: 'Unauthorized'), + ] + )] public function getAllMyOrders() { return $this->getAllMyOrdersBySummit('all'); @@ -412,25 +682,54 @@ public function getAllMyOrders() * @param $summit_id * @return mixed */ + #[OA\Get( + path: '/api/v1/summits/{id}/orders/me', + summary: 'Get all my orders for a summit', + description: 'Returns paginated list of current user orders for the specified summit', + operationId: 'getAllMyOrdersBySummit', + security: [ + [ + 'summit_orders_auth' => [ + SummitScopes::ReadMyRegistrationOrders, + ] + ] + ], + tags: ['Orders'], + parameters: [ + new OA\Parameter(name: 'id', in: 'path', required: true, description: 'Summit ID or slug (or "all" for all summits)', schema: new OA\Schema(type: 'string')), + new OA\Parameter(name: 'page', in: 'query', required: false, description: 'Page number', schema: new OA\Schema(type: 'integer', default: 1)), + new OA\Parameter(name: 'per_page', in: 'query', required: false, description: 'Items per page', schema: new OA\Schema(type: 'integer', default: 10)), + new OA\Parameter(name: 'filter', in: 'query', required: false, description: 'Filter criteria', schema: new OA\Schema(type: 'string')), + new OA\Parameter(name: 'order', in: 'query', required: false, description: 'Sort order', schema: new OA\Schema(type: 'string')), + ], + responses: [ + new OA\Response( + response: Response::HTTP_OK, + description: 'Paginated list of my orders', + content: new OA\JsonContent(ref: '#/components/schemas/PaginatedSummitOrdersResponse') + ), + new OA\Response(response: Response::HTTP_UNAUTHORIZED, description: 'Unauthorized'), + ] + )] public function getAllMyOrdersBySummit($summit_id) { $owner = $this->getResourceServerContext()->getCurrentUser(); - return $this->withReplica(function() use ($owner, $summit_id) { + return $this->withReplica(function () use ($owner, $summit_id) { return $this->_getAll( function () { return [ - 'number' => ['=@', '==','@@'], + 'number' => ['=@', '==', '@@'], 'summit_id' => ['=='], 'status' => ['==', '<>'], 'owner_id' => ['=='], - 'created' => ['>', '<', '<=', '>=', '==','[]'], - 'tickets_number' => ['=@', '==','@@'], - 'tickets_assigned_to' => ['=='], - 'tickets_owner_status' => ['=='], - 'tickets_owner_email' => ['=@', '==','@@'], - 'tickets_badge_features_id' => ['=='], - 'tickets_type_id' => ['=='], + 'created' => ['>', '<', '<=', '>=', '==', '[]'], + 'tickets_number' => ['=@', '==', '@@'], + 'tickets_assigned_to' => ['=='], + 'tickets_owner_status' => ['=='], + 'tickets_owner_email' => ['=@', '==', '@@'], + 'tickets_badge_features_id' => ['=='], + 'tickets_type_id' => ['=='], 'amount' => ['==', '<>', '>=', '>'], 'tickets_promo_code' => ['=@', '=='], ]; @@ -470,9 +769,9 @@ function ($filter) use ($owner, $summit_id) { $filter->addFilterCondition(FilterElement::makeEqual('summit_id', intval($summit_id))); } $filter->addFilterCondition(FilterElement::makeEqual('owner_id', $owner->getId())); - if($filter->hasFilter("tickets_assigned_to")){ + if ($filter->hasFilter("tickets_assigned_to")) { $assigned_to = $filter->getValue("tickets_assigned_to")[0]; - if(in_array($assigned_to, ['Me','SomeoneElse'])){ + if (in_array($assigned_to, ['Me', 'SomeoneElse'])) { $filter->addFilterCondition(FilterElement::makeEqual('tickets_owner_member_id', $owner->getId())); $filter->addFilterCondition(FilterElement::makeEqual('tickets_owner_member_email', $owner->getEmail())); } @@ -492,6 +791,33 @@ function () { * @param $ticket_id * @return mixed */ + #[OA\Get( + path: '/api/v1/summits/all/orders/{order_id}/tickets/{ticket_id}', + summary: 'Get my ticket by ID', + description: 'Returns ticket information for the current user by order and ticket ID', + operationId: 'getMyTicketById', + security: [ + [ + 'summit_orders_auth' => [ + SummitScopes::ReadMyRegistrationOrders, + ] + ] + ], + tags: ['Orders'], + parameters: [ + new OA\Parameter(name: 'order_id', in: 'path', required: true, description: 'Order ID', schema: new OA\Schema(type: 'integer')), + new OA\Parameter(name: 'ticket_id', in: 'path', required: true, description: 'Ticket ID', schema: new OA\Schema(type: 'integer')), + ], + responses: [ + new OA\Response( + response: Response::HTTP_OK, + description: 'Ticket information', + content: new OA\JsonContent(ref: '#/components/schemas/SummitAttendeeTicketPrivate') + ), + new OA\Response(response: Response::HTTP_UNAUTHORIZED, description: 'Unauthorized'), + new OA\Response(response: Response::HTTP_NOT_FOUND, description: 'Order or ticket not found'), + ] + )] public function getMyTicketById($order_id, $ticket_id) { @@ -511,12 +837,12 @@ public function getMyTicketById($order_id, $ticket_id) if ($order->getOwnerEmail() != $current_user->getEmail()) $isOrderOwner = false; - return $this->withReplica(function() use ($order, $ticket_id, $isOrderOwner, $current_user) { + return $this->withReplica(function () use ($order, $ticket_id, $isOrderOwner, $current_user) { $ticket = $order->getTicketById(intval($ticket_id)); if (!$ticket instanceof SummitAttendeeTicket) throw new EntityNotFoundException("Ticket not found."); - if(!$ticket->hasOwner() && !$isOrderOwner) + if (!$ticket->hasOwner() && !$isOrderOwner) throw new EntityNotFoundException("Order not found."); $isTicketOwner = true; @@ -531,16 +857,16 @@ public function getMyTicketById($order_id, $ticket_id) ) ); - if(!empty($ticketOwnerEmail) && $ticketOwnerEmail != $current_user->getEmail()) + if (!empty($ticketOwnerEmail) && $ticketOwnerEmail != $current_user->getEmail()) $isTicketOwner = false; - if(!$isTicketOwner){ + if (!$isTicketOwner) { // check if we are the manager $isTicketOwner = $ticket->hasOwner() && $ticket->getOwner()->isManagedBy($current_user); - Log::debug(sprintf("OAuth2SummitOrdersApiController::getMyTicketById isTicketOwner %b (manager)" , $isTicketOwner)); + Log::debug(sprintf("OAuth2SummitOrdersApiController::getMyTicketById isTicketOwner %b (manager)", $isTicketOwner)); } - if(!$isOrderOwner && !$isTicketOwner) + if (!$isOrderOwner && !$isTicketOwner) throw new EntityNotFoundException("Ticket not found."); return $this->ok(SerializerRegistry::getInstance() @@ -566,6 +892,38 @@ protected function getSummitRepository(): ISummitRepository /** * @param $order_id */ + #[OA\Put( + path: '/api/v1/summits/all/orders/{order_id}', + summary: 'Update my order', + description: 'Updates order information for the current user', + operationId: 'updateMyOrder', + security: [ + [ + 'summit_orders_auth' => [ + SummitScopes::UpdateMyRegistrationOrders, + ] + ] + ], + tags: ['Orders'], + parameters: [ + new OA\Parameter(name: 'order_id', in: 'path', required: true, description: 'Order ID', schema: new OA\Schema(type: 'integer')), + ], + requestBody: new OA\RequestBody( + required: false, + content: new OA\JsonContent(ref: '#/components/schemas/UpdateMyOrderRequest') + ), + responses: [ + new OA\Response( + response: Response::HTTP_CREATED, + description: 'Order updated successfully', + content: new OA\JsonContent(ref: '#/components/schemas/SummitOrder') + ), + new OA\Response(response: Response::HTTP_BAD_REQUEST, description: 'Bad Request'), + new OA\Response(response: Response::HTTP_UNAUTHORIZED, description: 'Unauthorized'), + new OA\Response(response: Response::HTTP_NOT_FOUND, description: 'Order not found'), + new OA\Response(response: Response::HTTP_PRECONDITION_FAILED, description: 'Validation Error'), + ] + )] public function updateMyOrder($order_id) { return $this->processRequest(function () use ($order_id) { @@ -584,13 +942,14 @@ public function updateMyOrder($order_id) $order = $this->service->updateMyOrder($current_user, intval($order_id), $payload); - return $this->created(SerializerRegistry::getInstance() - ->getSerializer($order, ISummitOrderSerializerTypes::CheckOutType) - ->serialize( - SerializerUtils::getExpand(), - SerializerUtils::getFields(), - SerializerUtils::getRelations() - ) + return $this->created( + SerializerRegistry::getInstance() + ->getSerializer($order, ISummitOrderSerializerTypes::CheckOutType) + ->serialize( + SerializerUtils::getExpand(), + SerializerUtils::getFields(), + SerializerUtils::getRelations() + ) ); }); } @@ -601,6 +960,43 @@ public function updateMyOrder($order_id) * @param $ticket_id * @return \Illuminate\Http\JsonResponse|mixed */ + #[OA\Delete( + path: '/api/v1/summits/all/orders/{order_id}/tickets/{ticket_id}/refund/cancel', + summary: 'Cancel refund request for a ticket', + description: 'Cancels an existing refund request for a ticket', + operationId: 'cancelRefundRequestTicket', + security: [ + [ + 'summit_orders_auth' => [ + SummitScopes::WriteSummitData, + SummitScopes::UpdateRegistrationOrders, + ] + ] + ], + x: [ + 'authz_groups' => [ + IGroup::SuperAdmins, + IGroup::Administrators, + IGroup::SummitAdministrators, + IGroup::SummitRegistrationAdmins, + ] + ], + tags: ['Orders'], + parameters: [ + new OA\Parameter(name: 'order_id', in: 'path', required: true, description: 'Order ID', schema: new OA\Schema(type: 'integer')), + new OA\Parameter(name: 'ticket_id', in: 'path', required: true, description: 'Ticket ID', schema: new OA\Schema(type: 'integer')), + ], + responses: [ + new OA\Response( + response: Response::HTTP_OK, + description: 'Refund request cancelled successfully', + content: new OA\JsonContent(ref: '#/components/schemas/SummitAttendeeTicket') + ), + new OA\Response(response: Response::HTTP_UNAUTHORIZED, description: 'Unauthorized'), + new OA\Response(response: Response::HTTP_FORBIDDEN, description: 'Forbidden'), + new OA\Response(response: Response::HTTP_NOT_FOUND, description: 'Order or ticket not found'), + ] + )] public function cancelRefundRequestTicket($order_id, $ticket_id) { return $this->processRequest(function () use ($order_id, $ticket_id) { @@ -614,13 +1010,14 @@ public function cancelRefundRequestTicket($order_id, $ticket_id) $ticket = $this->service->cancelRequestRefundTicket(intval($order_id), intval($ticket_id), $current_user, trim($payload['notes'] ?? '')); - return $this->updated(SerializerRegistry::getInstance()->getSerializer($ticket) - ->serialize - ( - SerializerUtils::getExpand(), - SerializerUtils::getFields(), - SerializerUtils::getRelations() - ) + return $this->updated( + SerializerRegistry::getInstance()->getSerializer($ticket) + ->serialize + ( + SerializerUtils::getExpand(), + SerializerUtils::getFields(), + SerializerUtils::getRelations() + ) ); }); } @@ -630,6 +1027,34 @@ public function cancelRefundRequestTicket($order_id, $ticket_id) * @param $ticket_id * @return \Illuminate\Http\JsonResponse|mixed */ + #[OA\Delete( + path: '/api/v1/summits/all/orders/{order_id}/tickets/{ticket_id}/refund', + summary: 'Request refund for a ticket', + description: 'Requests a refund for a specific ticket', + operationId: 'requestRefundMyTicket', + security: [ + [ + 'summit_orders_auth' => [ + SummitScopes::UpdateMyRegistrationOrders, + ] + ] + ], + tags: ['Orders'], + parameters: [ + new OA\Parameter(name: 'order_id', in: 'path', required: true, description: 'Order ID', schema: new OA\Schema(type: 'integer')), + new OA\Parameter(name: 'ticket_id', in: 'path', required: true, description: 'Ticket ID', schema: new OA\Schema(type: 'integer')), + ], + responses: [ + new OA\Response( + response: Response::HTTP_OK, + description: 'Refund requested successfully', + content: new OA\JsonContent(ref: '#/components/schemas/SummitAttendeeTicket') + ), + new OA\Response(response: Response::HTTP_UNAUTHORIZED, description: 'Unauthorized'), + new OA\Response(response: Response::HTTP_FORBIDDEN, description: 'Forbidden'), + new OA\Response(response: Response::HTTP_NOT_FOUND, description: 'Order or ticket not found'), + ] + )] public function requestRefundMyTicket($order_id, $ticket_id) { return $this->processRequest(function () use ($order_id, $ticket_id) { @@ -653,6 +1078,32 @@ public function requestRefundMyTicket($order_id, $ticket_id) * @param $order_id * @return \Illuminate\Http\JsonResponse|mixed */ + #[OA\Delete( + path: '/api/v1/summits/all/orders/{order_id}/refund', + summary: 'Request refund for entire order', + description: 'Requests a refund for all tickets in an order', + operationId: 'requestRefundMyOrder', + security: [ + [ + 'summit_orders_auth' => [ + SummitScopes::UpdateMyRegistrationOrders, + ] + ] + ], + tags: ['Orders'], + parameters: [ + new OA\Parameter(name: 'order_id', in: 'path', required: true, description: 'Order ID', schema: new OA\Schema(type: 'integer')), + ], + responses: [ + new OA\Response( + response: Response::HTTP_OK, + description: 'Refund requested successfully', + content: new OA\JsonContent(ref: '#/components/schemas/SummitOrder') + ), + new OA\Response(response: Response::HTTP_UNAUTHORIZED, description: 'Unauthorized'), + new OA\Response(response: Response::HTTP_NOT_FOUND, description: 'Order not found'), + ] + )] public function requestRefundMyOrder($order_id) { return $this->processRequest(function () use ($order_id) { @@ -674,6 +1125,39 @@ public function requestRefundMyOrder($order_id) * @param $ticket_id * @return \Illuminate\Http\JsonResponse|mixed */ + #[OA\Put( + path: '/api/v1/summits/all/orders/{order_id}/tickets/{ticket_id}/attendee', + summary: 'Assign attendee to a ticket', + description: 'Assigns an attendee to a specific ticket in an order', + operationId: 'assignAttendee', + security: [ + [ + 'summit_orders_auth' => [ + SummitScopes::UpdateMyRegistrationOrders, + ] + ] + ], + tags: ['Orders'], + parameters: [ + new OA\Parameter(name: 'order_id', in: 'path', required: true, description: 'Order ID', schema: new OA\Schema(type: 'integer')), + new OA\Parameter(name: 'ticket_id', in: 'path', required: true, description: 'Ticket ID', schema: new OA\Schema(type: 'integer')), + ], + requestBody: new OA\RequestBody( + required: true, + content: new OA\JsonContent(ref: '#/components/schemas/AssignAttendeeRequest') + ), + responses: [ + new OA\Response( + response: Response::HTTP_OK, + description: 'Attendee assigned successfully', + content: new OA\JsonContent(ref: '#/components/schemas/SummitAttendeeTicket') + ), + new OA\Response(response: Response::HTTP_BAD_REQUEST, description: 'Bad Request'), + new OA\Response(response: Response::HTTP_UNAUTHORIZED, description: 'Unauthorized'), + new OA\Response(response: Response::HTTP_NOT_FOUND, description: 'Order or ticket not found'), + new OA\Response(response: Response::HTTP_PRECONDITION_FAILED, description: 'Validation Error'), + ] + )] public function assignAttendee($order_id, $ticket_id) { return $this->processRequest(function () use ($order_id, $ticket_id) { @@ -704,6 +1188,42 @@ public function assignAttendee($order_id, $ticket_id) * @param $order_id * @return \Illuminate\Http\JsonResponse|mixed */ + #[OA\Put( + path: '/api/v1/summits/all/orders/{order_id}/resend', + summary: 'Resend order confirmation email', + description: 'Resends the order confirmation email. Admin access required.', + operationId: 'reSendOrderEmail', + security: [ + [ + 'summit_orders_auth' => [ + SummitScopes::WriteSummitData, + SummitScopes::UpdateRegistrationOrders, + ] + ] + ], + x: [ + 'authz_groups' => [ + IGroup::SuperAdmins, + IGroup::Administrators, + IGroup::SummitAdministrators, + IGroup::SummitRegistrationAdmins, + ] + ], + tags: ['Orders'], + parameters: [ + new OA\Parameter(name: 'order_id', in: 'path', required: true, description: 'Order ID', schema: new OA\Schema(type: 'integer')), + ], + responses: [ + new OA\Response( + response: Response::HTTP_OK, + description: 'Email sent successfully', + content: new OA\JsonContent(ref: '#/components/schemas/SummitOrder') + ), + new OA\Response(response: Response::HTTP_UNAUTHORIZED, description: 'Unauthorized'), + new OA\Response(response: Response::HTTP_FORBIDDEN, description: 'Forbidden'), + new OA\Response(response: Response::HTTP_NOT_FOUND, description: 'Order not found'), + ] + )] public function reSendOrderEmail($order_id) { return $this->processRequest(function () use ($order_id) { @@ -723,6 +1243,38 @@ public function reSendOrderEmail($order_id) * @param $ticket_id * @return \Illuminate\Http\JsonResponse|mixed */ + #[OA\Put( + path: '/api/v1/summits/all/orders/{order_id}/tickets/{ticket_id}/attendee/reinvite', + summary: 'Re-invite attendee to ticket', + description: 'Resends invitation email to the ticket attendee', + operationId: 'reInviteAttendee', + security: [ + [ + 'summit_orders_auth' => [ + SummitScopes::UpdateMyRegistrationOrders, + ] + ] + ], + tags: ['Orders'], + parameters: [ + new OA\Parameter(name: 'order_id', in: 'path', required: true, description: 'Order ID', schema: new OA\Schema(type: 'integer')), + new OA\Parameter(name: 'ticket_id', in: 'path', required: true, description: 'Ticket ID', schema: new OA\Schema(type: 'integer')), + ], + requestBody: new OA\RequestBody( + required: false, + content: new OA\JsonContent(ref: '#/components/schemas/ReInviteAttendeeRequest') + ), + responses: [ + new OA\Response( + response: Response::HTTP_OK, + description: 'Invitation sent successfully', + content: new OA\JsonContent(ref: '#/components/schemas/SummitAttendeeTicket') + ), + new OA\Response(response: Response::HTTP_UNAUTHORIZED, description: 'Unauthorized'), + new OA\Response(response: Response::HTTP_FORBIDDEN, description: 'Forbidden'), + new OA\Response(response: Response::HTTP_NOT_FOUND, description: 'Ticket not found'), + ] + )] public function reInviteAttendee($order_id, $ticket_id) { return $this->processRequest(function () use ($order_id, $ticket_id) { @@ -759,11 +1311,56 @@ public function reInviteAttendee($order_id, $ticket_id) * @param $ticket_id * @return \Illuminate\Http\JsonResponse|mixed */ + #[OA\Put( + path: '/api/v1/summits/{id}/orders/{order_id}/tickets/{ticket_id}', + summary: 'Update ticket details', + description: 'Updates ticket information including type, badge, and attendee details. Admin access required.', + operationId: 'updateTicket', + security: [ + [ + 'summit_orders_auth' => [ + SummitScopes::WriteSummitData, + SummitScopes::UpdateRegistrationOrders, + ] + ] + ], + x: [ + 'authz_groups' => [ + IGroup::SuperAdmins, + IGroup::Administrators, + IGroup::SummitAdministrators, + IGroup::SummitRegistrationAdmins, + ] + ], + tags: ['Orders'], + parameters: [ + new OA\Parameter(name: 'id', in: 'path', required: true, description: 'Summit ID or slug', schema: new OA\Schema(type: 'string')), + new OA\Parameter(name: 'order_id', in: 'path', required: true, description: 'Order ID', schema: new OA\Schema(type: 'integer')), + new OA\Parameter(name: 'ticket_id', in: 'path', required: true, description: 'Ticket ID', schema: new OA\Schema(type: 'integer')), + ], + requestBody: new OA\RequestBody( + required: false, + content: new OA\JsonContent(ref: '#/components/schemas/UpdateTicketRequest') + ), + responses: [ + new OA\Response( + response: Response::HTTP_OK, + description: 'Ticket updated successfully', + content: new OA\JsonContent(ref: '#/components/schemas/SummitAttendeeTicketAdmin') + ), + new OA\Response(response: Response::HTTP_BAD_REQUEST, description: 'Bad Request'), + new OA\Response(response: Response::HTTP_UNAUTHORIZED, description: 'Unauthorized'), + new OA\Response(response: Response::HTTP_FORBIDDEN, description: 'Forbidden'), + new OA\Response(response: Response::HTTP_NOT_FOUND, description: 'Summit, order or ticket not found'), + new OA\Response(response: Response::HTTP_PRECONDITION_FAILED, description: 'Validation Error'), + ] + )] public function updateTicket($summit_id, $order_id, $ticket_id) { return $this->processRequest(function () use ($summit_id, $order_id, $ticket_id) { $summit = SummitFinderStrategyFactory::build($this->summit_repository, $this->getResourceServerContext())->find($summit_id); - if (is_null($summit)) return $this->error404(); + if (is_null($summit)) + return $this->error404(); $payload = $this->getJsonPayload([ 'ticket_type_id' => 'nullable|integer', @@ -795,12 +1392,56 @@ public function updateTicket($summit_id, $order_id, $ticket_id) * @param $ticket_id * @return \Illuminate\Http\JsonResponse|mixed */ + #[OA\Post( + path: '/api/v1/summits/{id}/orders/{order_id}/tickets', + summary: 'Add tickets to an existing order', + description: 'Adds new tickets to an existing order. Admin access required.', + operationId: 'addTicket', + security: [ + [ + 'summit_orders_auth' => [ + SummitScopes::WriteSummitData, + SummitScopes::UpdateRegistrationOrders, + ] + ] + ], + x: [ + 'authz_groups' => [ + IGroup::SuperAdmins, + IGroup::Administrators, + IGroup::SummitAdministrators, + IGroup::SummitRegistrationAdmins, + ] + ], + tags: ['Orders'], + parameters: [ + new OA\Parameter(name: 'id', in: 'path', required: true, description: 'Summit ID or slug', schema: new OA\Schema(type: 'string')), + new OA\Parameter(name: 'order_id', in: 'path', required: true, description: 'Order ID', schema: new OA\Schema(type: 'integer')), + ], + requestBody: new OA\RequestBody( + required: true, + content: new OA\JsonContent(ref: '#/components/schemas/AddTicketRequest') + ), + responses: [ + new OA\Response( + response: Response::HTTP_CREATED, + description: 'Tickets added successfully', + content: new OA\JsonContent(ref: '#/components/schemas/SummitOrder') + ), + new OA\Response(response: Response::HTTP_BAD_REQUEST, description: 'Bad Request'), + new OA\Response(response: Response::HTTP_UNAUTHORIZED, description: 'Unauthorized'), + new OA\Response(response: Response::HTTP_FORBIDDEN, description: 'Forbidden'), + new OA\Response(response: Response::HTTP_NOT_FOUND, description: 'Summit or order not found'), + new OA\Response(response: Response::HTTP_PRECONDITION_FAILED, description: 'Validation Error'), + ] + )] public function addTicket($summit_id, $order_id) { return $this->processRequest(function () use ($summit_id, $order_id) { $summit = SummitFinderStrategyFactory::build($this->summit_repository, $this->getResourceServerContext())->find($summit_id); - if (is_null($summit)) return $this->error404(); + if (is_null($summit)) + return $this->error404(); $payload = $this->getJsonPayload([ 'ticket_type_id' => 'required|integer', @@ -833,6 +1474,33 @@ public function addTicket($summit_id, $order_id) * @param $ticket_id * @return \Illuminate\Http\JsonResponse|mixed */ + #[OA\Delete( + path: '/api/v1/summits/all/orders/{order_id}/tickets/{ticket_id}/attendee', + summary: 'Remove attendee from ticket', + description: 'Revokes/removes the attendee assignment from a ticket', + operationId: 'removeAttendee', + security: [ + [ + 'summit_orders_auth' => [ + SummitScopes::UpdateMyRegistrationOrders, + ] + ] + ], + tags: ['Orders'], + parameters: [ + new OA\Parameter(name: 'order_id', in: 'path', required: true, description: 'Order ID', schema: new OA\Schema(type: 'integer')), + new OA\Parameter(name: 'ticket_id', in: 'path', required: true, description: 'Ticket ID', schema: new OA\Schema(type: 'integer')), + ], + responses: [ + new OA\Response( + response: Response::HTTP_OK, + description: 'Attendee removed successfully', + content: new OA\JsonContent(ref: '#/components/schemas/SummitAttendeeTicket') + ), + new OA\Response(response: Response::HTTP_UNAUTHORIZED, description: 'Unauthorized'), + new OA\Response(response: Response::HTTP_NOT_FOUND, description: 'Order or ticket not found'), + ] + )] public function removeAttendee($order_id, $ticket_id) { @@ -840,12 +1508,13 @@ public function removeAttendee($order_id, $ticket_id) $current_user = $this->getResourceServerContext()->getCurrentUser(); $ticket = $this->service->revokeTicket($current_user, intval($order_id), intval($ticket_id)); - return $this->updated(SerializerRegistry::getInstance()->getSerializer($ticket) - ->serialize( - SerializerUtils::getExpand(), - SerializerUtils::getFields(), - SerializerUtils::getRelations() - ) + return $this->updated( + SerializerRegistry::getInstance()->getSerializer($ticket) + ->serialize( + SerializerUtils::getExpand(), + SerializerUtils::getFields(), + SerializerUtils::getRelations() + ) ); }); @@ -857,11 +1526,53 @@ public function removeAttendee($order_id, $ticket_id) * @param $ticket_id * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\Response|mixed */ + #[OA\Get( + path: '/api/v1/summits/{id}/orders/{order_id}/tickets/{ticket_id}/pdf', + summary: 'Get ticket PDF by summit', + description: 'Generates and returns ticket PDF. Admin access required.', + operationId: 'getTicketPDFBySummit', + security: [ + [ + 'summit_orders_auth' => [ + SummitScopes::ReadAllSummitData, + SummitScopes::ReadRegistrationOrders, + ] + ] + ], + x: [ + 'authz_groups' => [ + IGroup::SuperAdmins, + IGroup::Administrators, + IGroup::SummitAdministrators, + IGroup::SummitRegistrationAdmins, + ] + ], + tags: ['Orders'], + parameters: [ + new OA\Parameter(name: 'id', in: 'path', required: true, description: 'Summit ID or slug', schema: new OA\Schema(type: 'string')), + new OA\Parameter(name: 'order_id', in: 'path', required: true, description: 'Order ID', schema: new OA\Schema(type: 'integer')), + new OA\Parameter(name: 'ticket_id', in: 'path', required: true, description: 'Ticket ID', schema: new OA\Schema(type: 'integer')), + ], + responses: [ + new OA\Response( + response: Response::HTTP_OK, + description: 'PDF file', + content: new OA\MediaType( + mediaType: 'application/pdf', + schema: new OA\Schema(type: 'string', format: 'binary') + ) + ), + new OA\Response(response: Response::HTTP_UNAUTHORIZED, description: 'Unauthorized'), + new OA\Response(response: Response::HTTP_FORBIDDEN, description: 'Forbidden'), + new OA\Response(response: Response::HTTP_NOT_FOUND, description: 'Summit, order or ticket not found'), + ] + )] public function getTicketPDFBySummit($summit_id, $order_id, $ticket_id) { return $this->processRequest(function () use ($summit_id, $order_id, $ticket_id) { $summit = SummitFinderStrategyFactory::build($this->summit_repository, $this->getResourceServerContext())->find($summit_id); - if (is_null($summit)) return $this->error404(); + if (is_null($summit)) + return $this->error404(); $content = $this->service->renderTicketByFormat(intval($ticket_id), IRenderersFormats::PDFFormat, null, intval($order_id), $summit); return $this->pdf('ticket_' . $ticket_id . '.pdf', $content); }); @@ -872,6 +1583,36 @@ public function getTicketPDFBySummit($summit_id, $order_id, $ticket_id) * @param $ticket_id * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\Response|mixed */ + #[OA\Get( + path: '/api/v1/summits/all/orders/{order_id}/tickets/{ticket_id}/pdf', + summary: 'Get ticket PDF by order ID', + description: 'Generates and returns ticket PDF for current user', + operationId: 'getTicketPDFByOrderId', + security: [ + [ + 'summit_orders_auth' => [ + SummitScopes::ReadMyRegistrationOrders, + ] + ] + ], + tags: ['Orders'], + parameters: [ + new OA\Parameter(name: 'order_id', in: 'path', required: true, description: 'Order ID', schema: new OA\Schema(type: 'integer')), + new OA\Parameter(name: 'ticket_id', in: 'path', required: true, description: 'Ticket ID', schema: new OA\Schema(type: 'integer')), + ], + responses: [ + new OA\Response( + response: Response::HTTP_OK, + description: 'PDF file', + content: new OA\MediaType( + mediaType: 'application/pdf', + schema: new OA\Schema(type: 'string', format: 'binary') + ) + ), + new OA\Response(response: Response::HTTP_UNAUTHORIZED, description: 'Unauthorized'), + new OA\Response(response: Response::HTTP_NOT_FOUND, description: 'Order or ticket not found'), + ] + )] public function getTicketPDFByOrderId($order_id, $ticket_id) { return $this->processRequest(function () use ($order_id, $ticket_id) { @@ -885,6 +1626,35 @@ public function getTicketPDFByOrderId($order_id, $ticket_id) * @param $ticket_id * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\Response|mixed */ + #[OA\Get( + path: '/api/v1/summits/all/orders/all/tickets/{ticket_id}/pdf', + summary: 'Get my ticket PDF by ticket ID', + description: 'Generates and returns PDF for current user ticket', + operationId: 'getMyTicketPDFById', + security: [ + [ + 'summit_orders_auth' => [ + SummitScopes::ReadMyRegistrationOrders, + ] + ] + ], + tags: ['Orders'], + parameters: [ + new OA\Parameter(name: 'ticket_id', in: 'path', required: true, description: 'Ticket ID', schema: new OA\Schema(type: 'integer')), + ], + responses: [ + new OA\Response( + response: Response::HTTP_OK, + description: 'PDF file', + content: new OA\MediaType( + mediaType: 'application/pdf', + schema: new OA\Schema(type: 'string', format: 'binary') + ) + ), + new OA\Response(response: Response::HTTP_UNAUTHORIZED, description: 'Unauthorized'), + new OA\Response(response: Response::HTTP_NOT_FOUND, description: 'Ticket not found'), + ] + )] public function getMyTicketPDFById($ticket_id) { return $this->processRequest(function () use ($ticket_id) { @@ -900,6 +1670,24 @@ public function getMyTicketPDFById($ticket_id) * @param $hash * @return \Illuminate\Http\JsonResponse|mixed */ + #[OA\Get( + path: '/api/public/v1/summits/all/orders/all/tickets/{hash}', + summary: 'Get ticket by hash (public endpoint)', + description: 'Returns ticket information using public hash. No authentication required.', + operationId: 'getTicketByHash', + tags: ['Orders (Public)'], + parameters: [ + new OA\Parameter(name: 'hash', in: 'path', required: true, description: 'Ticket hash', schema: new OA\Schema(type: 'string')), + ], + responses: [ + new OA\Response( + response: Response::HTTP_OK, + description: 'Ticket information', + content: new OA\JsonContent(ref: '#/components/schemas/SummitAttendeeTicketPublic') + ), + new OA\Response(response: Response::HTTP_NOT_FOUND, description: 'Ticket not found or not active'), + ] + )] public function getTicketByHash($hash) { return $this->processRequest(function () use ($hash) { @@ -920,6 +1708,30 @@ public function getTicketByHash($hash) * @param $hash * @return \Illuminate\Http\JsonResponse|mixed */ + #[OA\Put( + path: '/api/public/v1/summits/all/orders/all/tickets/{hash}', + summary: 'Update ticket by hash (public endpoint)', + description: 'Updates ticket information using public hash. No authentication required.', + operationId: 'updateTicketByHash', + tags: ['Orders (Public)'], + parameters: [ + new OA\Parameter(name: 'hash', in: 'path', required: true, description: 'Ticket hash', schema: new OA\Schema(type: 'string')), + ], + requestBody: new OA\RequestBody( + required: false, + content: new OA\JsonContent(ref: '#/components/schemas/UpdateTicketByHashRequest') + ), + responses: [ + new OA\Response( + response: Response::HTTP_OK, + description: 'Ticket updated successfully', + content: new OA\JsonContent(ref: '#/components/schemas/SummitAttendeeTicket') + ), + new OA\Response(response: Response::HTTP_BAD_REQUEST, description: 'Bad Request'), + new OA\Response(response: Response::HTTP_NOT_FOUND, description: 'Ticket not found'), + new OA\Response(response: Response::HTTP_PRECONDITION_FAILED, description: 'Validation Error'), + ] + )] public function updateTicketByHash($hash) { return $this->processRequest(function () use ($hash) { @@ -950,6 +1762,30 @@ public function updateTicketByHash($hash) * @param $order_hash * @return \Illuminate\Http\JsonResponse|mixed */ + #[OA\Put( + path: '/api/public/v1/summits/all/orders/{order_hash}/tickets', + summary: 'Update tickets by order hash', + description: 'Updates multiple tickets information using order hash. No authentication required.', + operationId: 'updateTicketsByOrderHash', + tags: ['Orders (Public)'], + parameters: [ + new OA\Parameter(name: 'order_hash', in: 'path', required: true, description: 'Order hash', schema: new OA\Schema(type: 'string')), + ], + requestBody: new OA\RequestBody( + required: true, + content: new OA\JsonContent(ref: '#/components/schemas/UpdateTicketsByOrderHashRequest') + ), + responses: [ + new OA\Response( + response: Response::HTTP_OK, + description: 'Tickets updated successfully', + content: new OA\JsonContent(ref: '#/components/schemas/SummitOrder') + ), + new OA\Response(response: Response::HTTP_BAD_REQUEST, description: 'Bad Request'), + new OA\Response(response: Response::HTTP_NOT_FOUND, description: 'Order not found'), + new OA\Response(response: Response::HTTP_PRECONDITION_FAILED, description: 'Validation Error'), + ] + )] public function updateTicketsByOrderHash($order_hash) { return $this->processRequest(function () use ($order_hash) { @@ -974,6 +1810,39 @@ public function updateTicketsByOrderHash($order_hash) * @param $ticket_id * @return \Illuminate\Http\JsonResponse|mixed */ + #[OA\Put( + path: '/api/v1/summits/all/orders/all/tickets/{ticket_id}', + summary: 'Update my ticket by ticket ID', + description: 'Updates ticket information for the current user', + operationId: 'updateMyTicketById', + security: [ + [ + 'summit_orders_auth' => [ + SummitScopes::UpdateMyRegistrationOrders, + ] + ] + ], + tags: ['Orders'], + parameters: [ + new OA\Parameter(name: 'ticket_id', in: 'path', required: true, description: 'Ticket ID', schema: new OA\Schema(type: 'integer')), + ], + requestBody: new OA\RequestBody( + required: false, + content: new OA\JsonContent(ref: '#/components/schemas/UpdateTicketByHashRequest') + ), + responses: [ + new OA\Response( + response: Response::HTTP_OK, + description: 'Ticket updated successfully', + content: new OA\JsonContent(ref: '#/components/schemas/SummitAttendeeTicket') + ), + new OA\Response(response: Response::HTTP_BAD_REQUEST, description: 'Bad Request'), + new OA\Response(response: Response::HTTP_UNAUTHORIZED, description: 'Unauthorized'), + new OA\Response(response: Response::HTTP_FORBIDDEN, description: 'Forbidden'), + new OA\Response(response: Response::HTTP_NOT_FOUND, description: 'Ticket not found'), + new OA\Response(response: Response::HTTP_PRECONDITION_FAILED, description: 'Validation Error'), + ] + )] public function updateMyTicketById($ticket_id) { return $this->processRequest(function () use ($ticket_id) { @@ -1008,6 +1877,20 @@ public function updateMyTicketById($ticket_id) * @param $hash * @return \Illuminate\Http\JsonResponse|mixed */ + #[OA\Put( + path: '/api/public/v1/summits/all/orders/all/tickets/{hash}/regenerate', + summary: 'Regenerate ticket hash', + description: 'Regenerates the public hash for a ticket. No authentication required.', + operationId: 'regenerateTicketHash', + tags: ['Orders (Public)'], + parameters: [ + new OA\Parameter(name: 'hash', in: 'path', required: true, description: 'Current ticket hash', schema: new OA\Schema(type: 'string')), + ], + responses: [ + new OA\Response(response: Response::HTTP_OK, description: 'Hash regenerated successfully'), + new OA\Response(response: Response::HTTP_NOT_FOUND, description: 'Ticket not found'), + ] + )] public function regenerateTicketHash($hash) { return $this->processRequest(function () use ($hash) { @@ -1022,6 +1905,27 @@ public function regenerateTicketHash($hash) * @param $hash * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\Response|mixed */ + #[OA\Get( + path: '/api/public/v1/summits/all/orders/all/tickets/{hash}/pdf', + summary: 'Get ticket PDF by hash', + description: 'Generates and returns ticket PDF using public hash. No authentication required.', + operationId: 'getTicketPDFByHash', + tags: ['Orders (Public)'], + parameters: [ + new OA\Parameter(name: 'hash', in: 'path', required: true, description: 'Ticket hash', schema: new OA\Schema(type: 'string')), + ], + responses: [ + new OA\Response( + response: Response::HTTP_OK, + description: 'PDF file', + content: new OA\MediaType( + mediaType: 'application/pdf', + schema: new OA\Schema(type: 'string', format: 'binary') + ) + ), + new OA\Response(response: Response::HTTP_NOT_FOUND, description: 'Ticket not found'), + ] + )] public function getTicketPDFByHash($hash) { return $this->processRequest(function () use ($hash) { @@ -1060,21 +1964,126 @@ function getAddValidationRules(array $payload): array * @param array $payload * @return IEntity */ + #[OA\Post( + path: '/api/v1/summits/{id}/orders', + summary: 'Create offline order', + description: 'Creates a new offline order for a summit. Admin access required.', + operationId: 'add', + security: [ + [ + 'summit_orders_auth' => [ + SummitScopes::WriteSummitData, + SummitScopes::CreateOfflineRegistrationOrders, + ] + ] + ], + x: [ + 'authz_groups' => [ + IGroup::SuperAdmins, + IGroup::Administrators, + IGroup::SummitAdministrators, + IGroup::SummitRegistrationAdmins, + ] + ], + tags: ['Orders'], + parameters: [ + new OA\Parameter(name: 'id', in: 'path', required: true, description: 'Summit ID or slug', schema: new OA\Schema(type: 'string')), + ], + requestBody: new OA\RequestBody( + required: true, + content: new OA\JsonContent(ref: '#/components/schemas/CreateOfflineOrderRequest') + ), + responses: [ + new OA\Response( + response: Response::HTTP_CREATED, + description: 'Order created successfully', + content: new OA\JsonContent(ref: '#/components/schemas/SummitOrder') + ), + new OA\Response(response: Response::HTTP_BAD_REQUEST, description: 'Bad Request'), + new OA\Response(response: Response::HTTP_UNAUTHORIZED, description: 'Unauthorized'), + new OA\Response(response: Response::HTTP_FORBIDDEN, description: 'Forbidden'), + new OA\Response(response: Response::HTTP_NOT_FOUND, description: 'Summit not found'), + new OA\Response(response: Response::HTTP_PRECONDITION_FAILED, description: 'Validation Error'), + ] + )] protected function addChild(Summit $summit, array $payload): IEntity { return $this->service->createOfflineOrder($summit, $payload); } + #[OA\Get( + path: '/api/v1/summits/{id}/orders/{order_id}', + summary: 'Get order by ID', + description: 'Returns order information for the specified order. Admin access required.', + operationId: 'get', + security: [ + [ + 'summit_orders_auth' => [ + SummitScopes::ReadAllSummitData, + SummitScopes::ReadRegistrationOrders, + ] + ] + ], + x: [ + 'authz_groups' => [ + IGroup::SuperAdmins, + IGroup::Administrators, + IGroup::SummitAdministrators, + IGroup::SummitRegistrationAdmins, + ] + ], + tags: ['Orders'], + parameters: [ + new OA\Parameter(name: 'id', in: 'path', required: true, description: 'Summit ID or slug', schema: new OA\Schema(type: 'string')), + new OA\Parameter(name: 'order_id', in: 'path', required: true, description: 'Order ID', schema: new OA\Schema(type: 'integer')), + ], + responses: [ + new OA\Response( + response: Response::HTTP_OK, + description: 'Order information', + content: new OA\JsonContent(ref: '#/components/schemas/SummitOrder') + ), + new OA\Response(response: Response::HTTP_UNAUTHORIZED, description: 'Unauthorized'), + new OA\Response(response: Response::HTTP_FORBIDDEN, description: 'Forbidden'), + new OA\Response(response: Response::HTTP_NOT_FOUND, description: 'Summit or order not found'), + ] + )] protected function getChildFromSummit(Summit $summit, $child_id): ?IEntity { return $summit->getOrderById($child_id); } /** - * @param $summit_id * @param $order_id * @return mixed */ + #[OA\Get( + path: '/api/v1/summits/all/orders/{order_id}', + summary: 'Get my order by ID', + description: 'Returns order information for the current user', + operationId: 'getMyOrderById', + security: [ + [ + 'summit_orders_auth' => [ + SummitScopes::ReadMyRegistrationOrders, + ] + ] + ], + tags: ['Orders'], + parameters: [ + new OA\Parameter(name: 'order_id', in: 'path', required: true, description: 'Order ID', schema: new OA\Schema(type: 'integer')), + ], + responses: [ + new OA\Response( + response: Response::HTTP_OK, + description: 'Order information', + content: new OA\JsonContent(ref: '#/components/schemas/SummitOrder') + ), + new OA\Response(response: Response::HTTP_UNAUTHORIZED, description: 'Unauthorized'), + new OA\Response(response: Response::HTTP_FORBIDDEN, description: 'Forbidden'), + new OA\Response(response: Response::HTTP_NOT_FOUND, description: 'Order not found'), + ] + )] public function getMyOrderById($order_id) { @@ -1107,6 +2116,40 @@ public function getMyOrderById($order_id) * @param $order_id * @return \Illuminate\Http\JsonResponse|mixed */ + #[OA\Get( + path: '/api/v1/summits/all/orders/{order_id}/tickets', + summary: 'Get my tickets by order ID', + description: 'Returns paginated list of tickets for a specific order owned by current user', + operationId: 'getMyTicketsByOrderId', + security: [ + [ + 'summit_orders_auth' => [ + SummitScopes::ReadMyRegistrationOrders, + ] + ] + ], + tags: ['Orders'], + parameters: [ + new OA\Parameter(name: 'order_id', in: 'path', required: true, description: 'Order ID', schema: new OA\Schema(type: 'integer')), + new OA\Parameter(name: 'page', in: 'query', required: false, description: 'Page number', schema: new OA\Schema(type: 'integer', default: 1)), + new OA\Parameter(name: 'per_page', in: 'query', required: false, description: 'Items per page', schema: new OA\Schema(type: 'integer', default: 10)), + new OA\Parameter(name: 'filter', in: 'query', required: false, description: 'Filter criteria', schema: new OA\Schema(type: 'string')), + new OA\Parameter(name: 'order', in: 'query', required: false, description: 'Sort order', schema: new OA\Schema(type: 'string')), + ], + requestBody: new OA\RequestBody( + content: new OA\JsonContent(ref: '#/components/schemas/GetMyTicketsByOrderIdRequest') + ), + responses: [ + new OA\Response( + response: Response::HTTP_OK, + description: 'Paginated list of tickets', + content: new OA\JsonContent(ref: '#/components/schemas/PaginatedSummitAttendeeTicketsResponse') + ), + new OA\Response(response: Response::HTTP_UNAUTHORIZED, description: 'Unauthorized'), + new OA\Response(response: Response::HTTP_FORBIDDEN, description: 'Forbidden'), + new OA\Response(response: Response::HTTP_NOT_FOUND, description: 'Order not found'), + ] + )] public function getMyTicketsByOrderId($order_id) { @@ -1126,15 +2169,15 @@ public function getMyTicketsByOrderId($order_id) function () { return [ 'number' => ['=@', '==', '@@'], - 'owner_email' => ['=@', '==', '@@'], + 'owner_email' => ['=@', '==', '@@'], 'order_id' => ['=='], 'order_owner_id' => ['=='], 'is_active' => ['=='], - 'assigned_to' => ['=='], - 'owner_status' => ['=='], - 'badge_features_id' => ['=='], + 'assigned_to' => ['=='], + 'owner_status' => ['=='], + 'badge_features_id' => ['=='], 'final_amount' => ['==', '<>', '>=', '>'], - 'ticket_type_id' => ['=='], + 'ticket_type_id' => ['=='], 'promo_code' => ['=@', '=='], ]; }, @@ -1169,9 +2212,9 @@ function ($filter) use ($owner, $order_id) { $filter->addFilterCondition(FilterElement::makeEqual('order_id', intval($order_id))); $filter->addFilterCondition(FilterElement::makeEqual('order_owner_id', $owner->getId())); $filter->addFilterCondition(FilterElement::makeEqual('status', IOrderConstants::PaidStatus)); - if($filter->hasFilter("assigned_to")){ + if ($filter->hasFilter("assigned_to")) { $assigned_to = $filter->getValue("assigned_to")[0]; - if(in_array($assigned_to, ['Me','SomeoneElse'])){ + if (in_array($assigned_to, ['Me', 'SomeoneElse'])) { $filter->addFilterCondition(FilterElement::makeEqual('owner_member_id', $owner->getId())); $filter->addFilterCondition(FilterElement::makeEqual('owner_member_email', $owner->getEmail())); } @@ -1182,7 +2225,9 @@ function ($filter) use ($owner, $order_id) { function () { return ISummitOrderSerializerTypes::AdminType; } - , null, null, + , + null, + null, function ($page, $per_page, $filter, $order, $applyExtraFilters) { return $this->ticket_repository->getAllByPage( new PagingInfo($page, $per_page), @@ -1229,6 +2274,49 @@ function getUpdateValidationRules(array $payload): array * @param array $payload * @return IEntity */ + #[OA\Put( + path: '/api/v1/summits/{id}/orders/{order_id}', + summary: 'Update order', + description: 'Updates order information. Admin access required.', + operationId: 'update', + security: [ + [ + 'summit_orders_auth' => [ + SummitScopes::WriteSummitData, + SummitScopes::UpdateRegistrationOrders, + ] + ] + ], + x: [ + 'authz_groups' => [ + IGroup::SuperAdmins, + IGroup::Administrators, + IGroup::SummitAdministrators, + IGroup::SummitRegistrationAdmins, + ] + ], + tags: ['Orders'], + parameters: [ + new OA\Parameter(name: 'id', in: 'path', required: true, description: 'Summit ID or slug', schema: new OA\Schema(type: 'string')), + new OA\Parameter(name: 'order_id', in: 'path', required: true, description: 'Order ID', schema: new OA\Schema(type: 'integer')), + ], + requestBody: new OA\RequestBody( + required: false, + content: new OA\JsonContent(ref: '#/components/schemas/UpdateOrderRequest') + ), + responses: [ + new OA\Response( + response: Response::HTTP_OK, + description: 'Order updated successfully', + content: new OA\JsonContent(ref: '#/components/schemas/SummitOrder') + ), + new OA\Response(response: Response::HTTP_BAD_REQUEST, description: 'Bad Request'), + new OA\Response(response: Response::HTTP_UNAUTHORIZED, description: 'Unauthorized'), + new OA\Response(response: Response::HTTP_FORBIDDEN, description: 'Forbidden'), + new OA\Response(response: Response::HTTP_NOT_FOUND, description: 'Summit or order not found'), + new OA\Response(response: Response::HTTP_PRECONDITION_FAILED, description: 'Validation Error'), + ] + )] protected function updateChild(Summit $summit, int $child_id, array $payload): IEntity { return $this->service->updateOrder($summit, $child_id, $payload); @@ -1239,6 +2327,39 @@ protected function updateChild(Summit $summit, int $child_id, array $payload): I * @param $child_id * @return void */ + #[OA\Delete( + path: '/api/v1/summits/{id}/orders/{order_id}', + summary: 'Delete order', + description: 'Deletes an order. Admin access required.', + operationId: 'delete', + security: [ + [ + 'summit_orders_auth' => [ + SummitScopes::WriteSummitData, + SummitScopes::DeleteRegistrationOrders, + ] + ] + ], + x: [ + 'authz_groups' => [ + IGroup::SuperAdmins, + IGroup::Administrators, + IGroup::SummitAdministrators, + IGroup::SummitRegistrationAdmins, + ] + ], + tags: ['Orders'], + parameters: [ + new OA\Parameter(name: 'id', in: 'path', required: true, description: 'Summit ID or slug', schema: new OA\Schema(type: 'string')), + new OA\Parameter(name: 'order_id', in: 'path', required: true, description: 'Order ID', schema: new OA\Schema(type: 'integer')), + ], + responses: [ + new OA\Response(response: Response::HTTP_NO_CONTENT, description: 'Order deleted successfully'), + new OA\Response(response: Response::HTTP_UNAUTHORIZED, description: 'Unauthorized'), + new OA\Response(response: Response::HTTP_FORBIDDEN, description: 'Forbidden'), + new OA\Response(response: Response::HTTP_NOT_FOUND, description: 'Summit or order not found'), + ] + )] protected function deleteChild(Summit $summit, $child_id): void { $this->service->deleteOrder($summit, intval($child_id)); @@ -1250,11 +2371,50 @@ protected function deleteChild(Summit $summit, $child_id): void * @param $ticket_id * @return \Illuminate\Http\JsonResponse|mixed */ + #[OA\Put( + path: '/api/v1/summits/{id}/orders/{order_id}/tickets/{ticket_id}/activate', + summary: 'Activate a ticket', + description: 'Activates an inactive ticket. Admin access required.', + operationId: 'activateTicket', + security: [ + [ + 'summit_orders_auth' => [ + SummitScopes::WriteSummitData, + SummitScopes::UpdateRegistrationOrders, + ] + ] + ], + x: [ + 'authz_groups' => [ + IGroup::SuperAdmins, + IGroup::Administrators, + IGroup::SummitAdministrators, + IGroup::SummitRegistrationAdmins, + ] + ], + tags: ['Orders'], + parameters: [ + new OA\Parameter(name: 'id', in: 'path', required: true, description: 'Summit ID or slug', schema: new OA\Schema(type: 'string')), + new OA\Parameter(name: 'order_id', in: 'path', required: true, description: 'Order ID', schema: new OA\Schema(type: 'integer')), + new OA\Parameter(name: 'ticket_id', in: 'path', required: true, description: 'Ticket ID', schema: new OA\Schema(type: 'integer')), + ], + responses: [ + new OA\Response( + response: Response::HTTP_OK, + description: 'Ticket activated successfully', + content: new OA\JsonContent(ref: '#/components/schemas/SummitAttendeeTicket') + ), + new OA\Response(response: Response::HTTP_UNAUTHORIZED, description: 'Unauthorized'), + new OA\Response(response: Response::HTTP_FORBIDDEN, description: 'Forbidden'), + new OA\Response(response: Response::HTTP_NOT_FOUND, description: 'Summit, order or ticket not found'), + ] + )] public function activateTicket($summit_id, $order_id, $ticket_id) { return $this->processRequest(function () use ($summit_id, $order_id, $ticket_id) { $summit = SummitFinderStrategyFactory::build($this->summit_repository, $this->getResourceServerContext())->find($summit_id); - if (is_null($summit)) return $this->error404(); + if (is_null($summit)) + return $this->error404(); $ticket = $this->service->activateTicket($summit, intval($order_id), intval($ticket_id)); return $this->updated(SerializerRegistry::getInstance() @@ -1267,11 +2427,56 @@ public function activateTicket($summit_id, $order_id, $ticket_id) }); } + /** + * @param $summit_id + * @param $order_id + * @param $ticket_id + * @return \Illuminate\Http\JsonResponse|mixed + */ + #[OA\Delete( + path: '/api/v1/summits/{id}/orders/{order_id}/tickets/{ticket_id}/activate', + summary: 'Deactivate a ticket', + description: 'Deactivates an active ticket. Admin access required.', + operationId: 'deActivateTicket', + security: [ + [ + 'summit_orders_auth' => [ + SummitScopes::WriteSummitData, + SummitScopes::UpdateRegistrationOrders, + ] + ] + ], + x: [ + 'authz_groups' => [ + IGroup::SuperAdmins, + IGroup::Administrators, + IGroup::SummitAdministrators, + IGroup::SummitRegistrationAdmins, + ] + ], + tags: ['Orders'], + parameters: [ + new OA\Parameter(name: 'id', in: 'path', required: true, description: 'Summit ID or slug', schema: new OA\Schema(type: 'string')), + new OA\Parameter(name: 'order_id', in: 'path', required: true, description: 'Order ID', schema: new OA\Schema(type: 'integer')), + new OA\Parameter(name: 'ticket_id', in: 'path', required: true, description: 'Ticket ID', schema: new OA\Schema(type: 'integer')), + ], + responses: [ + new OA\Response( + response: Response::HTTP_OK, + description: 'Ticket deactivated successfully', + content: new OA\JsonContent(ref: '#/components/schemas/SummitAttendeeTicket') + ), + new OA\Response(response: Response::HTTP_UNAUTHORIZED, description: 'Unauthorized'), + new OA\Response(response: Response::HTTP_FORBIDDEN, description: 'Forbidden'), + new OA\Response(response: Response::HTTP_NOT_FOUND, description: 'Summit, order or ticket not found'), + ] + )] public function deActivateTicket($summit_id, $order_id, $ticket_id) { return $this->processRequest(function () use ($summit_id, $order_id, $ticket_id) { $summit = SummitFinderStrategyFactory::build($this->summit_repository, $this->getResourceServerContext())->find($summit_id); - if (is_null($summit)) return $this->error404(); + if (is_null($summit)) + return $this->error404(); $ticket = $this->service->deActivateTicket($summit, intval($order_id), intval($ticket_id)); return $this->updated(SerializerRegistry::getInstance() ->getSerializer($ticket, ISummitAttendeeTicketSerializerTypes::AdminType) @@ -1288,9 +2493,50 @@ public function deActivateTicket($summit_id, $order_id, $ticket_id) * @param $order_id * @return \Illuminate\Http\JsonResponse|mixed */ - public function getAllRefundApprovedRequests($summit_id, $order_id){ + #[OA\Get( + path: '/api/v1/summits/{id}/orders/{order_id}/tickets/all/refund-requests/approved', + summary: 'Get all approved refund requests for an order', + description: 'Returns paginated list of approved refund requests for tickets in an order. Admin access required.', + operationId: 'getAllRefundApprovedRequests', + security: [ + [ + 'summit_orders_auth' => [ + SummitScopes::ReadAllSummitData, + SummitScopes::ReadRegistrationOrders, + ] + ] + ], + x: [ + 'authz_groups' => [ + IGroup::SuperAdmins, + IGroup::Administrators, + IGroup::SummitAdministrators, + IGroup::SummitRegistrationAdmins, + ] + ], + tags: ['Orders'], + parameters: [ + new OA\Parameter(name: 'id', in: 'path', required: true, description: 'Summit ID or slug', schema: new OA\Schema(type: 'string')), + new OA\Parameter(name: 'order_id', in: 'path', required: true, description: 'Order ID', schema: new OA\Schema(type: 'integer')), + new OA\Parameter(name: 'page', in: 'query', required: false, description: 'Page number', schema: new OA\Schema(type: 'integer', default: 1)), + new OA\Parameter(name: 'per_page', in: 'query', required: false, description: 'Items per page', schema: new OA\Schema(type: 'integer', default: 10)), + ], + responses: [ + new OA\Response( + response: Response::HTTP_OK, + description: 'Paginated list of approved refund requests', + content: new OA\JsonContent(ref: '#/components/schemas/PaginatedRefundRequestsResponse') + ), + new OA\Response(response: Response::HTTP_UNAUTHORIZED, description: 'Unauthorized'), + new OA\Response(response: Response::HTTP_FORBIDDEN, description: 'Forbidden'), + new OA\Response(response: Response::HTTP_NOT_FOUND, description: 'Summit not found'), + ] + )] + public function getAllRefundApprovedRequests($summit_id, $order_id) + { $summit = SummitFinderStrategyFactory::build($this->summit_repository, $this->getResourceServerContext())->find($summit_id); - if (is_null($summit)) return $this->error404(); + if (is_null($summit)) + return $this->error404(); return $this->_getAll( function () { @@ -1340,11 +2586,56 @@ function ($page, $per_page, $filter, $order, $applyExtraFilters) use ($summit) { * @param $ticket_id * @return mixed */ + #[OA\Put( + path: '/api/v1/summits/{id}/orders/{order_id}/tickets/{ticket_id}/delegate', + summary: 'Delegate ticket to another attendee', + description: 'Delegates/transfers ticket ownership to another attendee. Admin access required.', + operationId: 'delegateTicket', + security: [ + [ + 'summit_orders_auth' => [ + SummitScopes::WriteSummitData, + SummitScopes::UpdateRegistrationOrders, + ] + ] + ], + x: [ + 'authz_groups' => [ + IGroup::SuperAdmins, + IGroup::Administrators, + IGroup::SummitAdministrators, + IGroup::SummitRegistrationAdmins, + ] + ], + tags: ['Orders'], + parameters: [ + new OA\Parameter(name: 'id', in: 'path', required: true, description: 'Summit ID or slug', schema: new OA\Schema(type: 'string')), + new OA\Parameter(name: 'order_id', in: 'path', required: true, description: 'Order ID', schema: new OA\Schema(type: 'integer')), + new OA\Parameter(name: 'ticket_id', in: 'path', required: true, description: 'Ticket ID', schema: new OA\Schema(type: 'integer')), + ], + requestBody: new OA\RequestBody( + required: true, + content: new OA\JsonContent(ref: '#/components/schemas/DelegateTicketRequest') + ), + responses: [ + new OA\Response( + response: Response::HTTP_OK, + description: 'Ticket delegated successfully', + content: new OA\JsonContent(ref: '#/components/schemas/SummitAttendeeTicket') + ), + new OA\Response(response: Response::HTTP_BAD_REQUEST, description: 'Bad Request'), + new OA\Response(response: Response::HTTP_UNAUTHORIZED, description: 'Unauthorized'), + new OA\Response(response: Response::HTTP_FORBIDDEN, description: 'Forbidden'), + new OA\Response(response: Response::HTTP_NOT_FOUND, description: 'Summit, order or ticket not found'), + new OA\Response(response: Response::HTTP_PRECONDITION_FAILED, description: 'Validation Error'), + ] + )] public function delegateTicket($summit_id, $order_id, $ticket_id) { return $this->processRequest(function () use ($summit_id, $order_id, $ticket_id) { $summit = SummitFinderStrategyFactory::build($this->summit_repository, $this->getResourceServerContext())->find($summit_id); - if (is_null($summit)) return $this->error404(); + if (is_null($summit)) + return $this->error404(); $current_user = $this->getResourceServerContext()->getCurrentUser(); if (is_null($current_user)) diff --git a/app/Swagger/SummitRegistrationSchemas.php b/app/Swagger/SummitRegistrationSchemas.php index 6b3e2f895..ca879b4e3 100644 --- a/app/Swagger/SummitRegistrationSchemas.php +++ b/app/Swagger/SummitRegistrationSchemas.php @@ -4,4 +4,737 @@ use OpenApi\Attributes as OA; -// +#[OA\Schema( + schema: 'PaginatedSummitOrdersResponse', + allOf: [ + new OA\Schema(ref: '#/components/schemas/PaginateDataSchemaResponse'), + new OA\Schema( + type: 'object', + properties: [ + new OA\Property( + property: 'data', + type: 'array', + items: new OA\Items(ref: '#/components/schemas/SummitOrder') + ) + ] + ) + ] +)] +class PaginatedSummitOrdersResponseSchema +{ +} + +#[OA\Schema( + schema: 'PaginatedSummitAttendeeTicketsResponse', + allOf: [ + new OA\Schema(ref: '#/components/schemas/PaginateDataSchemaResponse'), + new OA\Schema( + type: 'object', + properties: [ + new OA\Property( + property: 'data', + type: 'array', + items: new OA\Items(ref: '#/components/schemas/SummitAttendeeTicket') + ) + ] + ) + ] +)] +class PaginatedSummitAttendeeTicketsResponseSchema +{ +} + +#[OA\Schema( + schema: 'PaginatedRefundRequestsResponse', + allOf: [ + new OA\Schema(ref: '#/components/schemas/PaginateDataSchemaResponse'), + new OA\Schema( + type: 'object', + properties: [ + new OA\Property( + property: 'data', + type: 'array', + items: new OA\Items(ref: '#/components/schemas/SummitAttendeeTicketRefundRequest') + ) + ] + ) + ] +)] +class PaginatedRefundRequestsResponseSchema +{ +} + +#[OA\Schema( + schema: 'ExtraQuestions', + description: 'Extra questions for SummitOrder reservations', + type: 'object', + properties: [ + new OA\Property(property: 'question_id', type: 'integer'), + new OA\Property(property: 'answer', type: 'string'), + ] +)] +class ExtraQuestionsSchema +{ +} + +#[OA\Schema( + schema: 'TicketRequest', + description: 'TicketRequest for SummitOrder reservations', + type: 'object', + properties: [ + new OA\Property(property: 'id', type: 'integer'), + new OA\Property(property: 'type_id', type: 'integer'), + new OA\Property(property: 'promo_code', type: 'string'), + new OA\Property(property: 'attendee_first_name', type: 'string'), + new OA\Property(property: 'attendee_last_name', type: 'string'), + new OA\Property(property: 'attendee_email', type: 'string'), + new OA\Property(property: 'extra_questions', type: 'array', items: new OA\Items(ref: '#/components/schemas/ExtraQuestions')) + ] +)] +class TicketRequestSchema +{ +} + +#[OA\Schema( + schema: 'GetMyTicketsByOrderIdRequest', + description: 'GetMyTicketsByOrderIdRequest for Ticket reservations list', + type: 'object', + properties: [ + new OA\Property(property: 'number', type: 'string'), + new OA\Property(property: 'owner_email', type: 'string'), + new OA\Property(property: 'order_id', type: 'integer'), + new OA\Property(property: 'order_owner_id', type: 'integer'), + new OA\Property(property: 'is_active', type: 'boolean'), + new OA\Property(property: 'assigned_to', type: 'string', enum: ['Me', 'SomeoneElse', 'Nobody']), + new OA\Property(property: 'owner_status', type: 'string', enum: ['Complete', 'Incomplete']), + new OA\Property(property: 'badge_features_id', type: 'integer'), + new OA\Property(property: 'final_amount', type: 'float'), + new OA\Property(property: 'ticket_type_id', type: 'integer'), + new OA\Property(property: 'promo_code', type: 'string'), + ] +)] +class GetMyTicketsByOrderIdRequestSchema +{ +} + +#[OA\Schema( + schema: 'ReserveOrderRequest', + type: 'object', + required: ['tickets'], + properties: [ + new OA\Property(property: 'owner_first_name', type: 'string', maxLength: 255, description: 'Required if no current user'), + new OA\Property(property: 'owner_last_name', type: 'string', maxLength: 255, description: 'Required if no current user'), + new OA\Property(property: 'owner_email', type: 'string', format: 'email', maxLength: 255, description: 'Required if no current user'), + new OA\Property(property: 'owner_company', type: 'string', maxLength: 255), + new OA\Property(property: 'owner_company_id', type: 'integer'), + new OA\Property( + property: 'tickets', + type: 'array', + items: new OA\Items(ref: '#/components/schemas/TicketRequest'), + description: 'Array of ticket DTOs' + ), + new OA\Property( + property: 'extra_questions', + type: 'array', + items: new OA\Items(ref: '#/components/schemas/ExtraQuestions'), + description: 'Array of extra question answers' + ), + ] +)] +class ReserveOrderRequestSchema +{ +} + + +#[OA\Schema( + schema: 'UpdateMyOrderRequest', + type: 'object', + properties: [ + new OA\Property(property: 'owner_company', type: 'string', maxLength: 255), + new OA\Property(property: 'billing_address_1', type: 'string', maxLength: 255), + new OA\Property(property: 'billing_address_2', type: 'string', maxLength: 255), + new OA\Property(property: 'billing_address_zip_code', type: 'string', maxLength: 255), + new OA\Property(property: 'billing_address_city', type: 'string', maxLength: 255), + new OA\Property(property: 'billing_address_state', type: 'string', maxLength: 255), + new OA\Property(property: 'billing_address_country', type: 'string', maxLength: 2, description: 'ISO Alpha-2 country code'), + new OA\Property( + property: 'extra_questions', + type: 'array', + items: new OA\Items(type: 'object'), + description: 'Array of extra question answers' + ), + ] +)] +class UpdateMyOrderRequestSchema +{ +} + +#[OA\Schema( + schema: 'AssignAttendeeRequest', + type: 'object', + required: ['attendee_email'], + properties: [ + new OA\Property(property: 'attendee_first_name', type: 'string', maxLength: 255), + new OA\Property(property: 'attendee_last_name', type: 'string', maxLength: 255), + new OA\Property(property: 'attendee_email', type: 'string', format: 'email', maxLength: 255), + new OA\Property(property: 'attendee_company', type: 'string', maxLength: 255), + new OA\Property(property: 'disclaimer_accepted', type: 'boolean'), + new OA\Property( + property: 'extra_questions', + type: 'array', + items: new OA\Items(type: 'object'), + description: 'Array of extra question answers' + ), + new OA\Property(property: 'message', type: 'string', maxLength: 1024, description: 'Optional message to the attendee'), + ] +)] +class AssignAttendeeRequestSchema +{ +} + +#[OA\Schema( + schema: 'UpdateTicketRequest', + type: 'object', + properties: [ + new OA\Property(property: 'ticket_type_id', type: 'integer'), + new OA\Property(property: 'badge_type_id', type: 'integer'), + new OA\Property(property: 'attendee_first_name', type: 'string', maxLength: 255), + new OA\Property(property: 'attendee_last_name', type: 'string', maxLength: 255), + new OA\Property(property: 'attendee_email', type: 'string', format: 'email', maxLength: 255), + new OA\Property(property: 'attendee_company', type: 'string', maxLength: 255), + new OA\Property(property: 'attendee_company_id', type: 'integer'), + new OA\Property(property: 'disclaimer_accepted', type: 'boolean'), + new OA\Property( + property: 'extra_questions', + type: 'array', + items: new OA\Items(type: 'object'), + description: 'Array of extra question answers' + ), + ] +)] +class UpdateTicketRequestSchema +{ +} + +#[OA\Schema( + schema: 'UpdateTicketByHashRequest', + type: 'object', + properties: [ + new OA\Property(property: 'attendee_first_name', type: 'string', maxLength: 255), + new OA\Property(property: 'attendee_last_name', type: 'string', maxLength: 255), + new OA\Property(property: 'attendee_company', type: 'string', maxLength: 255), + new OA\Property(property: 'attendee_company_id', type: 'integer'), + new OA\Property(property: 'disclaimer_accepted', type: 'boolean'), + new OA\Property(property: 'share_contact_info', type: 'boolean'), + new OA\Property( + property: 'extra_questions', + type: 'array', + items: new OA\Items(type: 'object'), + description: 'Array of extra question answers' + ), + ] +)] +class UpdateTicketByHashRequestSchema +{ +} + +#[OA\Schema( + schema: 'UpdateTicketsByOrderHashRequest', + type: 'object', + required: ['tickets'], + properties: [ + new OA\Property( + property: 'tickets', + type: 'array', + items: new OA\Items(type: 'object'), + description: 'Array of ticket DTOs to update' + ), + ] +)] +class UpdateTicketsByOrderHashRequestSchema +{ +} + +#[OA\Schema( + schema: 'AddTicketRequest', + type: 'object', + required: ['ticket_type_id', 'ticket_qty'], + properties: [ + new OA\Property(property: 'ticket_type_id', type: 'integer'), + new OA\Property(property: 'ticket_qty', type: 'integer', minimum: 1), + new OA\Property(property: 'promo_code', type: 'string'), + new OA\Property(property: 'badge_type_id', type: 'integer'), + new OA\Property(property: 'attendee_first_name', type: 'string', maxLength: 255), + new OA\Property(property: 'attendee_last_name', type: 'string', maxLength: 255), + new OA\Property(property: 'attendee_email', type: 'string', format: 'email', maxLength: 255), + new OA\Property(property: 'attendee_company', type: 'string', maxLength: 255), + new OA\Property(property: 'disclaimer_accepted', type: 'boolean'), + new OA\Property( + property: 'extra_questions', + type: 'array', + items: new OA\Items(type: 'object'), + description: 'Array of extra question answers' + ), + ] +)] +class AddTicketRequestSchema +{ +} + +#[OA\Schema( + schema: 'CreateOfflineOrderRequest', + type: 'object', + required: ['ticket_type_id', 'ticket_qty'], + properties: [ + new OA\Property(property: 'owner_first_name', type: 'string', maxLength: 255, description: 'Required without owner_id'), + new OA\Property(property: 'owner_last_name', type: 'string', maxLength: 255, description: 'Required without owner_id'), + new OA\Property(property: 'owner_email', type: 'string', format: 'email', maxLength: 255, description: 'Required without owner_id'), + new OA\Property(property: 'owner_id', type: 'integer', description: 'Required without owner names/email'), + new OA\Property(property: 'owner_company', type: 'string', maxLength: 255), + new OA\Property(property: 'ticket_type_id', type: 'integer'), + new OA\Property(property: 'ticket_qty', type: 'integer', minimum: 1), + new OA\Property(property: 'promo_code', type: 'string'), + new OA\Property(property: 'billing_address_1', type: 'string', maxLength: 255), + new OA\Property(property: 'billing_address_2', type: 'string', maxLength: 255), + new OA\Property(property: 'billing_address_zip_code', type: 'string', maxLength: 255), + new OA\Property(property: 'billing_address_city', type: 'string', maxLength: 255), + new OA\Property(property: 'billing_address_state', type: 'string', maxLength: 255), + new OA\Property(property: 'billing_address_country', type: 'string', maxLength: 2, description: 'ISO Alpha-2 country code'), + new OA\Property( + property: 'extra_questions', + type: 'array', + items: new OA\Items(type: 'object'), + description: 'Array of extra question answers' + ), + ] +)] +class CreateOfflineOrderRequestSchema +{ +} + +#[OA\Schema( + schema: 'UpdateOrderRequest', + type: 'object', + required: ['owner_company'], + properties: [ + new OA\Property(property: 'owner_first_name', type: 'string', maxLength: 255, description: 'Required without owner_id'), + new OA\Property(property: 'owner_last_name', type: 'string', maxLength: 255, description: 'Required without owner_id'), + new OA\Property(property: 'owner_email', type: 'string', format: 'email', maxLength: 255, description: 'Required without owner_id'), + new OA\Property(property: 'owner_id', type: 'integer', description: 'Required without owner names/email'), + new OA\Property(property: 'owner_company', type: 'string', maxLength: 255), + new OA\Property(property: 'billing_address_1', type: 'string', maxLength: 255), + new OA\Property(property: 'billing_address_2', type: 'string', maxLength: 255), + new OA\Property(property: 'billing_address_zip_code', type: 'string', maxLength: 255), + new OA\Property(property: 'billing_address_city', type: 'string', maxLength: 255), + new OA\Property(property: 'billing_address_state', type: 'string', maxLength: 255), + new OA\Property(property: 'billing_address_country', type: 'string', maxLength: 2, description: 'ISO Alpha-2 country code'), + new OA\Property( + property: 'extra_questions', + type: 'array', + items: new OA\Items(type: 'object'), + description: 'Array of extra question answers' + ), + ] +)] +class UpdateOrderRequestSchema +{ +} + +#[OA\Schema( + schema: 'CancelRefundRequestRequest', + type: 'object', + properties: [ + new OA\Property(property: 'notes', type: 'string', maxLength: 255), + ] +)] +class CancelRefundRequestRequestSchema +{ +} + +#[OA\Schema( + schema: 'ReInviteAttendeeRequest', + type: 'object', + properties: [ + new OA\Property(property: 'message', type: 'string', maxLength: 1024, description: 'Optional message to the attendee'), + ] +)] +class ReInviteAttendeeRequestSchema +{ +} + +#[OA\Schema( + schema: 'DelegateTicketRequest', + type: 'object', + required: ['attendee_first_name', 'attendee_last_name'], + properties: [ + new OA\Property(property: 'attendee_first_name', type: 'string', maxLength: 255), + new OA\Property(property: 'attendee_last_name', type: 'string', maxLength: 255), + new OA\Property(property: 'attendee_email', type: 'string', format: 'email', maxLength: 255), + new OA\Property(property: 'attendee_company', type: 'string', maxLength: 255), + new OA\Property(property: 'attendee_company_id', type: 'integer'), + new OA\Property(property: 'disclaimer_accepted', type: 'boolean'), + new OA\Property( + property: 'extra_questions', + type: 'array', + items: new OA\Items(type: 'object'), + description: 'Array of extra question answers' + ), + ] +)] +class DelegateTicketRequestSchema +{ +} + +// Response Schemas - Generic Base Schemas + +#[OA\Schema( + schema: 'SummitOrder', + description: 'Generic SummitOrder response - fields may vary based on context and serializer type', + type: 'object', + properties: [ + new OA\Property(property: 'id', type: 'integer'), + new OA\Property(property: 'number', type: 'string'), + new OA\Property(property: 'status', type: 'string', enum: ['Reserved', 'Paid', 'Cancelled', 'RefundRequested', 'Refunded', 'Confirmed', 'Error']), + new OA\Property(property: 'hash', type: 'string'), + new OA\Property(property: 'amount', type: 'number', format: 'float'), + new OA\Property(property: 'currency', type: 'string'), + new OA\Property(property: 'created', type: 'integer', description: 'Unix timestamp'), + new OA\Property(property: 'owner_email', type: 'string'), + new OA\Property(property: 'tickets', type: 'array', items: new OA\Items(type: 'object')), + ] +)] +class SummitOrderSchema +{ +} + +#[OA\Schema( + schema: 'SummitAttendeeTicket', + description: 'Generic SummitAttendeeTicket response - fields may vary based on context and serializer type', + type: 'object', + required: [ + 'id', + 'number', + 'status', + 'external_order_id', + 'external_attendee_id', + 'bought_date', + 'ticket_type_id', + 'owner_id', + 'order_id', + 'badge_id', + 'promo_code_id', + 'raw_cost', + 'net_selling_cost', + 'raw_cost_in_cents', + 'final_amount', + 'final_amount_in_cents', + 'discount', + 'discount_rate', + 'discount_in_cents', + 'refunded_amount', + 'refunded_amount_in_cents', + 'total_refunded_amount', + 'total_refunded_amount_in_cents', + 'currency', + 'currency_symbol', + 'taxes_amount', + 'taxes_amount_in_cents', + 'is_active', + 'qr_code', + 'owner_email', + 'created' + ], + properties: [ + new OA\Property(property: 'id', type: 'integer'), + new OA\Property(property: 'created', type: 'integer', description: 'Unix timestamp'), + new OA\Property(property: 'last_edited', type: 'integer', description: 'Unix timestamp'), + new OA\Property(property: 'number', type: 'string'), + new OA\Property(property: 'status', type: 'string'), + new OA\Property(property: 'external_order_id', type: 'string'), + new OA\Property(property: 'external_attendee_id', type: 'string'), + new OA\Property(property: 'bought_date', type: 'integer'), + new OA\Property(property: 'ticket_type_id', type: 'integer'), + new OA\Property(property: 'owner_id', type: 'integer'), + new OA\Property(property: 'order_id', type: 'integer'), + new OA\Property(property: 'badge_id', type: 'integer'), + new OA\Property(property: 'promo_code_id', type: 'integer'), + new OA\Property(property: 'raw_cost', type: 'float'), + new OA\Property(property: 'net_selling_cost', type: 'float'), + new OA\Property(property: 'raw_cost_in_cents', type: 'integer'), + new OA\Property(property: 'final_amount', type: 'float'), + new OA\Property(property: 'final_amount_in_cents', type: 'integer'), + new OA\Property(property: 'discount', type: 'float'), + new OA\Property(property: 'discount_rate', type: 'float'), + new OA\Property(property: 'discount_in_cents', type: 'integer'), + new OA\Property(property: 'refunded_amount', type: 'float'), + new OA\Property(property: 'refunded_amount_in_cents', type: 'integer'), + new OA\Property(property: 'total_refunded_amount', type: 'float'), + new OA\Property(property: 'total_refunded_amount_in_cents', type: 'integer'), + new OA\Property(property: 'currency', type: 'string'), + new OA\Property(property: 'currency_symbol', type: 'string'), + new OA\Property(property: 'taxes_amount', type: 'float'), + new OA\Property(property: 'taxes_amount_in_cents', type: 'integer'), + new OA\Property(property: 'is_active', type: 'boolean'), + new OA\Property(property: 'qr_code', type: 'string'), + new OA\Property(property: 'owner_email', type: 'string'), + new OA\Property(property: 'owner', ref: '#/components/schemas/SummitAttendee', description: 'Owner of the ticket, only available when ?expand=owner'), + new OA\Property(property: 'order', ref: '#/components/schemas/SummitOrder', description: 'Order associated to the ticket, only available when ?expand=order'), + ] +)] +class SummitAttendeeTicketSchema +{ +} + + +#[OA\Schema( + schema: 'SummitOrderReservation', + description: 'SummitOrder with ReservationType serialization - used for order reservations', + type: 'object', + required: [ + 'id', + 'created', + 'last_edited', + 'number', + 'status', + 'hash', + 'payment_method', + 'owner_first_name', + 'owner_last_name', + 'owner_email', + 'summit_id', + 'currency', + 'currency_symbol', + 'raw_amount', + 'raw_amount_in_cents', + 'amount', + 'amount_in_cents', + 'taxes_amount', + 'taxes_amount_in_cents', + 'discount_amount', + 'discount_rate', + 'discount_amount_in_cents', + 'payment_gateway_client_token', + 'payment_gateway_cart_id', + 'hash_creation_date', + 'refunded_amount', + 'refunded_amount_in_cents', + 'total_refunded_amount', + 'total_refunded_amount_in_cents', + 'credit_card_type', + 'credit_card_4number', + 'payment_info_type', + 'payment_info_details', + 'tickets' + ], + properties: [ + new OA\Property(property: 'id', type: 'integer'), + new OA\Property(property: 'created', type: 'integer', description: 'Unix timestamp'), + new OA\Property(property: 'last_edited', type: 'integer', description: 'Unix timestamp'), + new OA\Property(property: 'number', type: 'string'), + new OA\Property(property: 'status', type: 'string', enum: ['Reserved', 'Paid', 'Cancelled', 'RefundRequested', 'Refunded', 'Confirmed', 'Error']), + new OA\Property(property: 'hash', type: 'string'), + new OA\Property(property: 'payment_method', type: 'string'), + new OA\Property(property: 'owner_first_name', type: 'string'), + new OA\Property(property: 'owner_last_name', type: 'string'), + new OA\Property(property: 'owner_email', type: 'string'), + new OA\Property(property: 'owner_company', type: ['string', 'Company']), + new OA\Property(property: 'owner_company_id', type: 'integer'), + new OA\Property(property: 'owner_id', type: 'integer'), + new OA\Property(property: 'owner', type: 'Member'), + new OA\Property(property: 'summit_id', type: 'integer'), + new OA\Property(property: 'currency', type: 'string'), + new OA\Property(property: 'currency_symbol', type: 'string'), + new OA\Property(property: 'raw_amount', type: 'float'), + new OA\Property(property: 'raw_amount_in_cents', type: 'integer'), + new OA\Property(property: 'amount', type: 'number', format: 'float'), + new OA\Property(property: 'amount_in_cents', type: 'integer'), + new OA\Property(property: 'taxes_amount', type: 'number', format: 'float'), + new OA\Property(property: 'taxes_amount_in_cents', type: 'integer'), + new OA\Property(property: 'discount_amount', type: 'number', format: 'float'), + new OA\Property(property: 'discount_rate', type: 'float'), + new OA\Property(property: 'discount_amount_in_cents', type: 'integer'), + new OA\Property(property: 'payment_gateway_client_token', type: 'string'), + new OA\Property(property: 'payment_gateway_cart_id', type: 'string'), + new OA\Property(property: 'hash_creation_date', type: 'integer', description: 'Unix timestamp'), + new OA\Property(property: 'refunded_amount', type: 'number', format: 'float'), + new OA\Property(property: 'refunded_amount_in_cents', type: 'integer', format: 'float'), + new OA\Property(property: 'total_refunded_amount', type: 'float', format: 'float'), + new OA\Property(property: 'total_refunded_amount_in_cents', type: 'integer', format: 'float'), + new OA\Property(property: 'credit_card_type', type: 'string'), + new OA\Property(property: 'credit_card_4number', type: 'string'), + new OA\Property(property: 'payment_info_type', type: 'string'), + new OA\Property(property: 'payment_info_details', type: 'string'), + new OA\Property(property: 'tickets', type: 'array', items: new OA\Items(type: 'SummitAttendeeTicket')), + new OA\Property(property: 'extra_questions', type: 'array', items: new OA\Items(ref: '#/components/schemas/ExtraQuestions')), + new OA\Property(property: 'applied_taxes', type: 'array', items: new OA\Items(type: 'SummitTaxType')), + + ] +)] +class SummitOrderReservationSchema +{ +} + +#[OA\Schema( + schema: 'CheckoutOrderRequest', + type: 'object', + properties: [ + new OA\Property(property: 'billing_address_1', type: 'string', maxLength: 255), + new OA\Property(property: 'billing_address_2', type: 'string', maxLength: 255), + new OA\Property(property: 'billing_address_zip_code', type: 'string', maxLength: 255), + new OA\Property(property: 'billing_address_city', type: 'string', maxLength: 255), + new OA\Property(property: 'billing_address_state', type: 'string', maxLength: 255), + new OA\Property(property: 'billing_address_country', type: 'string', maxLength: 2, description: 'ISO Alpha-2 country code'), + new OA\Property(property: 'payment_method_id', type: 'string'), + ] +)] +class CheckoutOrderRequestSchema +{ +} + +#[OA\Schema( + schema: 'SummitOrderCheckout', + description: 'SummitOrder with CheckOutType serialization - used after checkout/payment', + type: 'object', + properties: [ + new OA\Property(property: 'id', type: 'integer'), + new OA\Property(property: 'created', type: 'integer', description: 'Unix timestamp'), + new OA\Property(property: 'last_edited', type: 'integer', description: 'Unix timestamp'), + new OA\Property(property: 'number', type: 'string'), + new OA\Property(property: 'status', type: 'string', enum: ['Reserved', 'Paid', 'Cancelled', 'RefundRequested', 'Refunded', 'Confirmed', 'Error']), + new OA\Property(property: 'owner_first_name', type: 'string'), + new OA\Property(property: 'owner_last_name', type: 'string'), + new OA\Property(property: 'owner_email', type: 'string'), + new OA\Property(property: 'owner_company', type: ['string', 'Company']), + new OA\Property(property: 'owner_company_id', type: 'integer'), + new OA\Property(property: 'owner_id', type: 'integer'), + new OA\Property(property: 'summit_id', type: 'integer'), + new OA\Property(property: 'currency', type: 'string'), + new OA\Property(property: 'extra_questions', type: 'array', items: new OA\Items(oneOf: [new OA\Schema(type: 'integer'), new OA\Schema(ref: '#/components/schemas/ExtraQuestions')])), + new OA\Property(property: 'tickets', type: 'array', items: new OA\Items(oneOf: [new OA\Schema(type: 'integer'), new OA\Schema(ref: '#/components/schemas/SummitAttendeeTicket')])), + new OA\Property(property: 'owner', type: 'Member'), + ] +)] +class SummitOrderCheckoutSchema +{ +} + +#[OA\Schema( + schema: 'SummitAttendeeTicketBase', + description: 'Base SummitAttendeeTicket response - fields may vary based on context and serializer type', + type: 'object', + allOf: [ + new OA\Schema(ref: '#/components/schemas/SummitAttendeeTicket'), + ], + properties: [ + new OA\Property(property: 'ticket_type', type: 'array', items: new OA\Items(type: 'SummitTicketType')), + new OA\Property(property: 'badge', type: 'array', items: new OA\Items(type: 'SummitAttendeeBadge')), + new OA\Property(property: 'promo_code', type: 'array', items: new OA\Items(type: 'SummitRegistrationPromoCode')), + new OA\Property(property: 'owner', type: 'array', items: new OA\Items(type: 'SummitAttendee')), + new OA\Property(property: 'refund_requests', type: 'array', items: new OA\Items(type: ['integer', 'SummitAttendeeTicketRefundRequest'])), + new OA\Property(property: 'applied_taxes', type: 'array', items: new OA\Items(type: 'integer')), + new OA\Property(property: 'order', type: 'array', items: new OA\Items(type: 'SummitOrder')), + ] +)] +class SummitAttendeeTicketBaseSchema +{ +} + +#[OA\Schema( + schema: 'SummitAttendeeTicketPublic', + description: 'SummitAttendeeTicket with PublicEdition serialization - public ticket access', + type: 'object', + allOf: [ + new OA\Schema(ref: '#/components/schemas/SummitAttendeeTicketBase'), + new OA\Schema( + type: 'object', + properties: [ + new OA\Property(property: 'order_extra_questions', type: 'array', items: new OA\Items(type: 'SummitOrderExtraQuestionType')), + ] + ), + ], + properties: [] +)] +class SummitAttendeeTicketPublicSchema +{ +} + +#[OA\Schema( + schema: 'SummitAttendeeTicketPrivate', + ref: '#/components/schemas/SummitAttendeeTicketBase', +)] +class SummitAttendeeTicketPrivateSchema +{ +} + + +//@TODO Matu Checkear a partir de aqui si es necesario separar mas los schemas de ticket segun el contexto + +#[OA\Schema( + schema: 'SummitAttendeeTicketAdmin', + description: 'SummitAttendeeTicket with AdminType serialization - full admin access', + type: 'object', + allOf: [ + new OA\Schema(ref: '#/components/schemas/SummitAttendeeTicket'), + new OA\Schema( + type: 'object', + properties: [ + new OA\Property(property: 'owner', type: 'array', items: new OA\Items(type: 'SummitAttendee')), + new OA\Property(property: 'order', type: 'array', items: new OA\Items(type: 'SummitOrder')), + ] + ), + ], + +)] +class SummitAttendeeTicketAdminSchema +{ +} + +#[OA\Schema( + schema: 'SummitAttendeeTicketGuest', + description: 'SummitAttendeeTicket with Guest edition serialization', + type: 'object', + allOf: [ + new OA\Schema(ref: '#/components/schemas/SummitAttendeeTicketBase'), + new OA\Schema( + type: 'object', + properties: [ + new OA\Property(property: 'edit_link', type: 'array', items: new OA\Items(type: 'string')), + ] + ), + ], + +)] +class SummitAttendeeTicketGuestSchema +{ +} + +#[OA\Schema( + schema: 'SummitAttendeeTicketRefundRequest', + description: 'Refund request for a ticket', + type: 'object', + properties: [ + new OA\Property(property: 'id', type: 'integer'), + new OA\Property(property: 'created', type: 'integer', description: 'Unix timestamp'), + new OA\Property(property: 'last_edited', type: 'integer', description: 'Unix timestamp'), + new OA\Property(property: 'status', type: 'string', enum: ['Requested', 'Approved', 'Rejected']), + new OA\Property(property: 'refunded_amount', type: 'number', format: 'float'), + new OA\Property(property: 'refunded_amount_in_cents', type: 'integer'), + new OA\Property(property: 'taxes_refunded_amount', type: 'number', format: 'float'), + new OA\Property(property: 'taxes_refunded_amount_in_cents', type: 'integer'), + new OA\Property(property: 'total_refunded_amount', type: 'number', format: 'float'), + new OA\Property(property: 'total_refunded_amount_in_cents', type: 'integer'), + new OA\Property(property: 'notes', type: 'string'), + new OA\Property(property: 'payment_gateway_result', type: 'string'), + new OA\Property(property: 'action_date', type: 'integer', description: 'Unix timestamp'), + new OA\Property(property: 'requested_by_id', type: 'integer'), + new OA\Property(property: 'action_by_id', type: 'integer'), + new OA\Property(property: 'ticket_id', type: 'integer'), + new OA\Property(property: 'requested_by', type: 'Member', description: 'Expandable - Member who requested'), + new OA\Property(property: 'action_by', type: 'Member', description: 'Expandable - Member who approved/rejected'), + new OA\Property(property: 'ticket', type: 'SummitTicket', description: 'Expandable - Ticket'), + new OA\Property(property: 'refunded_taxes', type: 'array', items: new OA\Items(type: 'object'), description: 'Expandable - List of tax refunds'), + ] +)] +class SummitAttendeeTicketRefundRequestSchema +{ +} \ No newline at end of file