diff --git a/README.md b/README.md index aadbea20..37010c32 100644 --- a/README.md +++ b/README.md @@ -65,33 +65,48 @@ Get started in minutes with our one-click deployment options: Hi.Events offers comprehensive tools to streamline your event management: -### 🎟 Ticketing & Sales -- **Multiple Ticket Types:** Create free, paid, donation-based, and tiered tickets -- **Capacity Management:** Set limits per event or ticket type -- **Promo Codes & Discounts:** Drive early sales with special offers -- **Product Upsells:** Sell merchandise and add-ons alongside tickets -- **Custom Pricing:** Apply taxes and fees per product or entire order - -### 🏆 Event Management -- **Real-time Dashboard:** Track sales, revenue, and attendee metrics -- **Visual Page Editor:** Design beautiful event pages with live preview -- **Website Integration:** Embed ticketing widgets on your existing site -- **SEO Optimization:** Customize metadata for better search visibility -- **Offline Event Support:** Provide location details and instructions - -### 📱 Attendee Experience -- **Custom Registration Forms:** Collect exactly the information you need -- **QR Code Check-In:** Fast, mobile-friendly entry verification -- **Multi-language Support:** Reach global audiences with localized interfaces -- **Bulk Communication:** Send targeted messages to specific ticket holders -- **Refund Management:** Process full or partial refunds when needed - -### 🔧 For Organizers -- **Team Collaboration:** Role-based access for staff members -- **Webhook Integration:** Connect with Zapier, IFTTT, Make, or your CRM -- **Stripe Connect:** Receive instant payouts for ticket sales -- **Comprehensive API:** Build custom integrations with full API access -- **Advanced Reporting:** Generate sales, tax, and usage reports +### 🎟 Ticketing & Product Sales +- **Multiple Ticket Types:** Free, Paid, Donation, and Tiered tickets. +- **Capacity Management:** Set event-wide or ticket-specific limits. +- **Capacity Assignments:** Manage shared capacity across multiple ticket types. +- **Promo Codes:** Discount codes for pre-sale access and special offers. +- **Product Sales:** Sell event-related products (e.g., t-shirts, add-ons). +- **Taxes & Fees:** Apply custom taxes and fees per product or order. + +### 🏆 Event Management & Customization +- **Event Dashboard:** Real-time revenue, ticket sales, and attendee analytics. +- **Homepage Designer:** Customize event pages with a live preview editor. +- **Embeddable Ticket Widget:** Add a seamless ticketing experience to your website. +- **SEO Tools:** Customize event metadata for better search visibility. +- **Product Categories:** Organize products and tickets with category management. +- **Offline Event Support:** Provide instructions for physical events. + +### 📧 Attendee & Order Management +- **Custom Checkout Forms:** Collect attendee details with tailored questions. +- **Attendee Management:** Search, edit, cancel, and message attendees. +- **Order Management:** Refund, cancel, and resend order details easily. +- **Bulk Messaging:** Email or message specific ticket holders. +- **Data Exports:** Export attendees and orders to CSV/XLSX. + +### 📱 Mobile-Friendly & Check-In Tools +- **QR Code Check-In:** Web-based and mobile-friendly check-in tool. +- **Check-In Lists:** Generate and share access-controlled check-in lists. +- **Multi-User Access:** Role-based access control for event staff. + +### 🔧 Integrations & Automation +- **Webhooks Support:** Automate tasks with Zapier, IFTTT, Make, or CRM integrations. +- **Stripe Connect Integration:** Organizers get instant payouts. + +### 📊 Advanced Features +- **Multi-Language Support:** English, Deutsch, Español, Português, Français, 中文 (Zhōngwén), and more. +- **Partial & Full Refunds:** Manage refunds with detailed order tracking. +- **Role-Based Access Control:** Multiple user roles with permission management. +- **REST API:** Full API access for custom integrations. +- **Invoicing System:** Generate and send invoices with tax details, payment terms, and due dates. +- **Offline Payment Support:** Enable bank transfers, cash payments, or custom payment methods. +- **Event Archive:** Archive past events to keep the dashboard organized. +- **Advanced Ticket Locking:** Lock tickets behind promo codes or access restrictions. +- **Advanced Reporting:** Daily sales, tax breakdowns, product sales, and promo code usage reports. ## 🚀 Getting Started diff --git a/backend/app/Repository/Eloquent/BaseRepository.php b/backend/app/Repository/Eloquent/BaseRepository.php index 0a3364b7..aa174bd9 100644 --- a/backend/app/Repository/Eloquent/BaseRepository.php +++ b/backend/app/Repository/Eloquent/BaseRepository.php @@ -62,7 +62,10 @@ public function setMaxPerPage(int $maxPerPage): static public function all(array $columns = self::DEFAULT_COLUMNS): Collection { - return $this->handleResults($this->model->all($columns)); + $models = $this->model->all($columns); + $this->resetModel(); + + return $this->handleResults($models); } public function paginate( @@ -121,7 +124,10 @@ public function paginateEloquentRelation( */ public function findById(int $id, array $columns = self::DEFAULT_COLUMNS): DomainObjectInterface { - return $this->handleSingleResult($this->model->findOrFail($id, $columns)); + $model = $this->model->findOrFail($id, $columns); + $this->resetModel(); + + return $this->handleSingleResult($model); } public function findFirstByField( @@ -138,7 +144,10 @@ public function findFirstByField( public function findFirst(int $id, array $columns = self::DEFAULT_COLUMNS): ?DomainObjectInterface { - return $this->handleSingleResult($this->model->findOrFail($id, $columns)); + $model = $this->model->findOrFail($id, $columns); + $this->resetModel(); + + return $this->handleSingleResult($model); } public function findWhere( diff --git a/backend/app/Services/Application/Handlers/Order/CompleteOrderHandler.php b/backend/app/Services/Application/Handlers/Order/CompleteOrderHandler.php index 1604f253..bfeb1b58 100644 --- a/backend/app/Services/Application/Handlers/Order/CompleteOrderHandler.php +++ b/backend/app/Services/Application/Handlers/Order/CompleteOrderHandler.php @@ -62,7 +62,7 @@ public function __construct( */ public function handle(string $orderShortId, CompleteOrderDTO $orderData): OrderDomainObject { - return DB::transaction(function () use ($orderData, $orderShortId) { + $updatedOrder = DB::transaction(function () use ($orderData, $orderShortId) { $orderDTO = $orderData->order; $order = $this->getOrder($orderShortId); @@ -85,19 +85,21 @@ public function handle(string $orderShortId, CompleteOrderDTO $orderData): Order $this->productQuantityUpdateService->updateQuantitiesFromOrder($updatedOrder); } - OrderStatusChangedEvent::dispatch($updatedOrder); - - if ($updatedOrder->isOrderCompleted()) { - $this->domainEventDispatcherService->dispatch( - new OrderEvent( - type: DomainEventType::ORDER_CREATED, - orderId: $updatedOrder->getId(), - ) - ); - } - return $updatedOrder; }); + + OrderStatusChangedEvent::dispatch($updatedOrder); + + if ($updatedOrder->isOrderCompleted()) { + $this->domainEventDispatcherService->dispatch( + new OrderEvent( + type: DomainEventType::ORDER_CREATED, + orderId: $updatedOrder->getId(), + ) + ); + } + + return $updatedOrder; } /** diff --git a/backend/app/Services/Infrastructure/DomainEvents/DomainEventDispatcherService.php b/backend/app/Services/Infrastructure/DomainEvents/DomainEventDispatcherService.php index 5c833d86..e40847cd 100644 --- a/backend/app/Services/Infrastructure/DomainEvents/DomainEventDispatcherService.php +++ b/backend/app/Services/Infrastructure/DomainEvents/DomainEventDispatcherService.php @@ -4,15 +4,29 @@ use HiEvents\Services\Infrastructure\DomainEvents\Events\BaseDomainEvent; use Illuminate\Events\Dispatcher as EventDispatcher; +use Psr\Log\LoggerInterface; +use Throwable; class DomainEventDispatcherService { - public function __construct(private readonly EventDispatcher $dispatcher) + public function __construct( + private readonly EventDispatcher $dispatcher, + private readonly LoggerInterface $logger, + ) { } + /** + * @throws Throwable + */ public function dispatch(BaseDomainEvent $event): void { - $this->dispatcher->dispatch($event); + try { + $this->dispatcher->dispatch($event); + } catch (Throwable $e) { + $this->logger->error('Failed to dispatch domain event', ['event' => $event, 'exception' => $e]); + + throw $e; + } } }