diff --git a/backend/app/DomainObjects/AttendeeDomainObject.php b/backend/app/DomainObjects/AttendeeDomainObject.php index c8101dd736..ee9ceeb994 100644 --- a/backend/app/DomainObjects/AttendeeDomainObject.php +++ b/backend/app/DomainObjects/AttendeeDomainObject.php @@ -11,7 +11,7 @@ class AttendeeDomainObject extends Generated\AttendeeDomainObjectAbstract implem { private ?OrderDomainObject $order = null; - private ?TicketDomainObject $ticket = null; + private ?ProductDomainObject $product = null; /** @var Collection|null */ public ?Collection $questionAndAnswerViews = null; @@ -60,7 +60,7 @@ public static function getAllowedFilterFields(): array { return [ self::STATUS, - self::TICKET_ID, + self::PRODUCT_ID, ]; } @@ -79,14 +79,14 @@ public function getFullName(): string return $this->first_name . ' ' . $this->last_name; } - public function getTicket(): ?TicketDomainObject + public function getProduct(): ?ProductDomainObject { - return $this->ticket; + return $this->product; } - public function setTicket(?TicketDomainObject $ticket): self + public function setProduct(?ProductDomainObject $product): self { - $this->ticket = $ticket; + $this->product = $product; return $this; } diff --git a/backend/app/DomainObjects/CapacityAssignmentDomainObject.php b/backend/app/DomainObjects/CapacityAssignmentDomainObject.php index 758847d251..642fadae10 100644 --- a/backend/app/DomainObjects/CapacityAssignmentDomainObject.php +++ b/backend/app/DomainObjects/CapacityAssignmentDomainObject.php @@ -9,7 +9,7 @@ class CapacityAssignmentDomainObject extends Generated\CapacityAssignmentDomainObjectAbstract implements IsSortable { - public ?Collection $tickets = null; + public ?Collection $products = null; public static function getDefaultSort(): string { @@ -58,14 +58,14 @@ public function getPercentageUsed(): float return round(($this->getUsedCapacity() / $this->getCapacity()) * 100, 2); } - public function getTickets(): ?Collection + public function getProducts(): ?Collection { - return $this->tickets; + return $this->products; } - public function setTickets(?Collection $tickets): static + public function setProducts(?Collection $products): static { - $this->tickets = $tickets; + $this->products = $products; return $this; } diff --git a/backend/app/DomainObjects/CheckInListDomainObject.php b/backend/app/DomainObjects/CheckInListDomainObject.php index 30364989e6..ae55f3bbcf 100644 --- a/backend/app/DomainObjects/CheckInListDomainObject.php +++ b/backend/app/DomainObjects/CheckInListDomainObject.php @@ -9,7 +9,7 @@ class CheckInListDomainObject extends Generated\CheckInListDomainObjectAbstract implements IsSortable { - private ?Collection $tickets = null; + private ?Collection $products = null; private ?EventDomainObject $event = null; @@ -53,14 +53,14 @@ public static function getAllowedSorts(): AllowedSorts ); } - public function getTickets(): ?Collection + public function getProducts(): ?Collection { - return $this->tickets; + return $this->products; } - public function setTickets(?Collection $tickets): static + public function setProducts(?Collection $products): static { - $this->tickets = $tickets; + $this->products = $products; return $this; } diff --git a/backend/app/DomainObjects/Enums/CapacityAssignmentAppliesTo.php b/backend/app/DomainObjects/Enums/CapacityAssignmentAppliesTo.php index 75d27759b4..250b77a355 100644 --- a/backend/app/DomainObjects/Enums/CapacityAssignmentAppliesTo.php +++ b/backend/app/DomainObjects/Enums/CapacityAssignmentAppliesTo.php @@ -6,6 +6,6 @@ enum CapacityAssignmentAppliesTo { use BaseEnum; - case TICKETS; + case PRODUCTS; case EVENT; } diff --git a/backend/app/DomainObjects/Enums/MessageTypeEnum.php b/backend/app/DomainObjects/Enums/MessageTypeEnum.php index 4246d750b0..fd2b44b171 100644 --- a/backend/app/DomainObjects/Enums/MessageTypeEnum.php +++ b/backend/app/DomainObjects/Enums/MessageTypeEnum.php @@ -7,7 +7,7 @@ enum MessageTypeEnum use BaseEnum; case ORDER; - case TICKET; + case PRODUCT; case ATTENDEE; case EVENT; } diff --git a/backend/app/DomainObjects/Enums/TicketType.php b/backend/app/DomainObjects/Enums/ProductPriceType.php similarity index 87% rename from backend/app/DomainObjects/Enums/TicketType.php rename to backend/app/DomainObjects/Enums/ProductPriceType.php index 942a45156d..a0d01c442c 100644 --- a/backend/app/DomainObjects/Enums/TicketType.php +++ b/backend/app/DomainObjects/Enums/ProductPriceType.php @@ -2,7 +2,7 @@ namespace HiEvents\DomainObjects\Enums; -enum TicketType +enum ProductPriceType { use BaseEnum; diff --git a/backend/app/DomainObjects/Enums/ProductType.php b/backend/app/DomainObjects/Enums/ProductType.php new file mode 100644 index 0000000000..03a9b5b529 --- /dev/null +++ b/backend/app/DomainObjects/Enums/ProductType.php @@ -0,0 +1,11 @@ +tickets = $tickets; + $this->products = $products; return $this; } - public function getTickets(): ?Collection + public function getProducts(): ?Collection { - return $this->tickets; + return $this->products; } public function setQuestions(?Collection $questions): EventDomainObject @@ -259,4 +261,15 @@ public function setEventStatistics(?EventStatisticDomainObject $eventStatistics) $this->eventStatistics = $eventStatistics; return $this; } + + public function setProductCategories(?Collection $productCategories): EventDomainObject + { + $this->productCategories = $productCategories; + return $this; + } + + public function getProductCategories(): ?Collection + { + return $this->productCategories; + } } diff --git a/backend/app/DomainObjects/Generated/AttendeeCheckInDomainObjectAbstract.php b/backend/app/DomainObjects/Generated/AttendeeCheckInDomainObjectAbstract.php index ffb56a8d57..1249d2714c 100644 --- a/backend/app/DomainObjects/Generated/AttendeeCheckInDomainObjectAbstract.php +++ b/backend/app/DomainObjects/Generated/AttendeeCheckInDomainObjectAbstract.php @@ -12,7 +12,7 @@ abstract class AttendeeCheckInDomainObjectAbstract extends \HiEvents\DomainObjec final public const PLURAL_NAME = 'attendee_check_ins'; final public const ID = 'id'; final public const CHECK_IN_LIST_ID = 'check_in_list_id'; - final public const TICKET_ID = 'ticket_id'; + final public const PRODUCT_ID = 'product_id'; final public const ATTENDEE_ID = 'attendee_id'; final public const EVENT_ID = 'event_id'; final public const SHORT_ID = 'short_id'; @@ -23,7 +23,7 @@ abstract class AttendeeCheckInDomainObjectAbstract extends \HiEvents\DomainObjec protected int $id; protected int $check_in_list_id; - protected int $ticket_id; + protected int $product_id; protected int $attendee_id; protected int $event_id; protected string $short_id; @@ -37,7 +37,7 @@ public function toArray(): array return [ 'id' => $this->id ?? null, 'check_in_list_id' => $this->check_in_list_id ?? null, - 'ticket_id' => $this->ticket_id ?? null, + 'product_id' => $this->product_id ?? null, 'attendee_id' => $this->attendee_id ?? null, 'event_id' => $this->event_id ?? null, 'short_id' => $this->short_id ?? null, @@ -70,15 +70,15 @@ public function getCheckInListId(): int return $this->check_in_list_id; } - public function setTicketId(int $ticket_id): self + public function setProductId(int $product_id): self { - $this->ticket_id = $ticket_id; + $this->product_id = $product_id; return $this; } - public function getTicketId(): int + public function getProductId(): int { - return $this->ticket_id; + return $this->product_id; } public function setAttendeeId(int $attendee_id): self diff --git a/backend/app/DomainObjects/Generated/AttendeeDomainObjectAbstract.php b/backend/app/DomainObjects/Generated/AttendeeDomainObjectAbstract.php index 59b052de07..6097de8955 100644 --- a/backend/app/DomainObjects/Generated/AttendeeDomainObjectAbstract.php +++ b/backend/app/DomainObjects/Generated/AttendeeDomainObjectAbstract.php @@ -12,11 +12,11 @@ abstract class AttendeeDomainObjectAbstract extends \HiEvents\DomainObjects\Abst final public const PLURAL_NAME = 'attendees'; final public const ID = 'id'; final public const ORDER_ID = 'order_id'; - final public const TICKET_ID = 'ticket_id'; + final public const PRODUCT_ID = 'product_id'; final public const EVENT_ID = 'event_id'; final public const CHECKED_IN_BY = 'checked_in_by'; final public const CHECKED_OUT_BY = 'checked_out_by'; - final public const TICKET_PRICE_ID = 'ticket_price_id'; + final public const PRODUCT_PRICE_ID = 'product_price_id'; final public const SHORT_ID = 'short_id'; final public const FIRST_NAME = 'first_name'; final public const LAST_NAME = 'last_name'; @@ -31,11 +31,11 @@ abstract class AttendeeDomainObjectAbstract extends \HiEvents\DomainObjects\Abst protected int $id; protected int $order_id; - protected int $ticket_id; + protected int $product_id; protected int $event_id; protected ?int $checked_in_by = null; protected ?int $checked_out_by = null; - protected int $ticket_price_id; + protected int $product_price_id; protected string $short_id; protected string $first_name = ''; protected string $last_name = ''; @@ -53,11 +53,11 @@ public function toArray(): array return [ 'id' => $this->id ?? null, 'order_id' => $this->order_id ?? null, - 'ticket_id' => $this->ticket_id ?? null, + 'product_id' => $this->product_id ?? null, 'event_id' => $this->event_id ?? null, 'checked_in_by' => $this->checked_in_by ?? null, 'checked_out_by' => $this->checked_out_by ?? null, - 'ticket_price_id' => $this->ticket_price_id ?? null, + 'product_price_id' => $this->product_price_id ?? null, 'short_id' => $this->short_id ?? null, 'first_name' => $this->first_name ?? null, 'last_name' => $this->last_name ?? null, @@ -94,15 +94,15 @@ public function getOrderId(): int return $this->order_id; } - public function setTicketId(int $ticket_id): self + public function setProductId(int $product_id): self { - $this->ticket_id = $ticket_id; + $this->product_id = $product_id; return $this; } - public function getTicketId(): int + public function getProductId(): int { - return $this->ticket_id; + return $this->product_id; } public function setEventId(int $event_id): self @@ -138,15 +138,15 @@ public function getCheckedOutBy(): ?int return $this->checked_out_by; } - public function setTicketPriceId(int $ticket_price_id): self + public function setProductPriceId(int $product_price_id): self { - $this->ticket_price_id = $ticket_price_id; + $this->product_price_id = $product_price_id; return $this; } - public function getTicketPriceId(): int + public function getProductPriceId(): int { - return $this->ticket_price_id; + return $this->product_price_id; } public function setShortId(string $short_id): self diff --git a/backend/app/DomainObjects/Generated/EventDailyStatisticDomainObjectAbstract.php b/backend/app/DomainObjects/Generated/EventDailyStatisticDomainObjectAbstract.php index beae7bfa03..ac1749cb9c 100644 --- a/backend/app/DomainObjects/Generated/EventDailyStatisticDomainObjectAbstract.php +++ b/backend/app/DomainObjects/Generated/EventDailyStatisticDomainObjectAbstract.php @@ -15,7 +15,7 @@ abstract class EventDailyStatisticDomainObjectAbstract extends \HiEvents\DomainO final public const SALES_TOTAL_GROSS = 'sales_total_gross'; final public const TOTAL_TAX = 'total_tax'; final public const SALES_TOTAL_BEFORE_ADDITIONS = 'sales_total_before_additions'; - final public const TICKETS_SOLD = 'tickets_sold'; + final public const PRODUCTS_SOLD = 'products_sold'; final public const ORDERS_CREATED = 'orders_created'; final public const DATE = 'date'; final public const CREATED_AT = 'created_at'; @@ -25,13 +25,14 @@ abstract class EventDailyStatisticDomainObjectAbstract extends \HiEvents\DomainO final public const VERSION = 'version'; final public const TOTAL_REFUNDED = 'total_refunded'; final public const TOTAL_VIEWS = 'total_views'; + final public const ATTENDEES_REGISTERED = 'attendees_registered'; protected int $id; protected int $event_id; protected float $sales_total_gross = 0.0; protected float $total_tax = 0.0; protected float $sales_total_before_additions = 0.0; - protected int $tickets_sold = 0; + protected int $products_sold = 0; protected int $orders_created = 0; protected string $date; protected string $created_at; @@ -41,6 +42,7 @@ abstract class EventDailyStatisticDomainObjectAbstract extends \HiEvents\DomainO protected int $version = 0; protected float $total_refunded = 0.0; protected int $total_views = 0; + protected int $attendees_registered = 0; public function toArray(): array { @@ -50,7 +52,7 @@ public function toArray(): array 'sales_total_gross' => $this->sales_total_gross ?? null, 'total_tax' => $this->total_tax ?? null, 'sales_total_before_additions' => $this->sales_total_before_additions ?? null, - 'tickets_sold' => $this->tickets_sold ?? null, + 'products_sold' => $this->products_sold ?? null, 'orders_created' => $this->orders_created ?? null, 'date' => $this->date ?? null, 'created_at' => $this->created_at ?? null, @@ -60,6 +62,7 @@ public function toArray(): array 'version' => $this->version ?? null, 'total_refunded' => $this->total_refunded ?? null, 'total_views' => $this->total_views ?? null, + 'attendees_registered' => $this->attendees_registered ?? null, ]; } @@ -118,15 +121,15 @@ public function getSalesTotalBeforeAdditions(): float return $this->sales_total_before_additions; } - public function setTicketsSold(int $tickets_sold): self + public function setProductsSold(int $products_sold): self { - $this->tickets_sold = $tickets_sold; + $this->products_sold = $products_sold; return $this; } - public function getTicketsSold(): int + public function getProductsSold(): int { - return $this->tickets_sold; + return $this->products_sold; } public function setOrdersCreated(int $orders_created): self @@ -227,4 +230,15 @@ public function getTotalViews(): int { return $this->total_views; } + + public function setAttendeesRegistered(int $attendees_registered): self + { + $this->attendees_registered = $attendees_registered; + return $this; + } + + public function getAttendeesRegistered(): int + { + return $this->attendees_registered; + } } diff --git a/backend/app/DomainObjects/Generated/EventSettingDomainObjectAbstract.php b/backend/app/DomainObjects/Generated/EventSettingDomainObjectAbstract.php index a883a7e23b..03feec38a1 100644 --- a/backend/app/DomainObjects/Generated/EventSettingDomainObjectAbstract.php +++ b/backend/app/DomainObjects/Generated/EventSettingDomainObjectAbstract.php @@ -14,7 +14,7 @@ abstract class EventSettingDomainObjectAbstract extends \HiEvents\DomainObjects\ final public const EVENT_ID = 'event_id'; final public const PRE_CHECKOUT_MESSAGE = 'pre_checkout_message'; final public const POST_CHECKOUT_MESSAGE = 'post_checkout_message'; - final public const TICKET_PAGE_MESSAGE = 'ticket_page_message'; + final public const PRODUCT_PAGE_MESSAGE = 'product_page_message'; final public const CONTINUE_BUTTON_TEXT = 'continue_button_text'; final public const EMAIL_FOOTER_MESSAGE = 'email_footer_message'; final public const SUPPORT_EMAIL = 'support_email'; @@ -50,7 +50,7 @@ abstract class EventSettingDomainObjectAbstract extends \HiEvents\DomainObjects\ protected int $event_id; protected ?string $pre_checkout_message = null; protected ?string $post_checkout_message = null; - protected ?string $ticket_page_message = null; + protected ?string $product_page_message = null; protected ?string $continue_button_text = null; protected ?string $email_footer_message = null; protected ?string $support_email = null; @@ -89,7 +89,7 @@ public function toArray(): array 'event_id' => $this->event_id ?? null, 'pre_checkout_message' => $this->pre_checkout_message ?? null, 'post_checkout_message' => $this->post_checkout_message ?? null, - 'ticket_page_message' => $this->ticket_page_message ?? null, + 'product_page_message' => $this->product_page_message ?? null, 'continue_button_text' => $this->continue_button_text ?? null, 'email_footer_message' => $this->email_footer_message ?? null, 'support_email' => $this->support_email ?? null, @@ -167,15 +167,15 @@ public function getPostCheckoutMessage(): ?string return $this->post_checkout_message; } - public function setTicketPageMessage(?string $ticket_page_message): self + public function setProductPageMessage(?string $product_page_message): self { - $this->ticket_page_message = $ticket_page_message; + $this->product_page_message = $product_page_message; return $this; } - public function getTicketPageMessage(): ?string + public function getProductPageMessage(): ?string { - return $this->ticket_page_message; + return $this->product_page_message; } public function setContinueButtonText(?string $continue_button_text): self diff --git a/backend/app/DomainObjects/Generated/EventStatisticDomainObjectAbstract.php b/backend/app/DomainObjects/Generated/EventStatisticDomainObjectAbstract.php index 1519b8fbe9..e92c464041 100644 --- a/backend/app/DomainObjects/Generated/EventStatisticDomainObjectAbstract.php +++ b/backend/app/DomainObjects/Generated/EventStatisticDomainObjectAbstract.php @@ -21,10 +21,11 @@ abstract class EventStatisticDomainObjectAbstract extends \HiEvents\DomainObject final public const DELETED_AT = 'deleted_at'; final public const UPDATED_AT = 'updated_at'; final public const TOTAL_FEE = 'total_fee'; - final public const TICKETS_SOLD = 'tickets_sold'; + final public const PRODUCTS_SOLD = 'products_sold'; final public const VERSION = 'version'; final public const ORDERS_CREATED = 'orders_created'; final public const TOTAL_REFUNDED = 'total_refunded'; + final public const ATTENDEES_REGISTERED = 'attendees_registered'; protected int $id; protected int $event_id; @@ -37,10 +38,11 @@ abstract class EventStatisticDomainObjectAbstract extends \HiEvents\DomainObject protected ?string $deleted_at = null; protected ?string $updated_at = null; protected float $total_fee = 0.0; - protected int $tickets_sold = 0; + protected int $products_sold = 0; protected int $version = 0; protected int $orders_created = 0; protected float $total_refunded = 0.0; + protected int $attendees_registered = 0; public function toArray(): array { @@ -56,10 +58,11 @@ public function toArray(): array 'deleted_at' => $this->deleted_at ?? null, 'updated_at' => $this->updated_at ?? null, 'total_fee' => $this->total_fee ?? null, - 'tickets_sold' => $this->tickets_sold ?? null, + 'products_sold' => $this->products_sold ?? null, 'version' => $this->version ?? null, 'orders_created' => $this->orders_created ?? null, 'total_refunded' => $this->total_refunded ?? null, + 'attendees_registered' => $this->attendees_registered ?? null, ]; } @@ -184,15 +187,15 @@ public function getTotalFee(): float return $this->total_fee; } - public function setTicketsSold(int $tickets_sold): self + public function setProductsSold(int $products_sold): self { - $this->tickets_sold = $tickets_sold; + $this->products_sold = $products_sold; return $this; } - public function getTicketsSold(): int + public function getProductsSold(): int { - return $this->tickets_sold; + return $this->products_sold; } public function setVersion(int $version): self @@ -227,4 +230,15 @@ public function getTotalRefunded(): float { return $this->total_refunded; } + + public function setAttendeesRegistered(int $attendees_registered): self + { + $this->attendees_registered = $attendees_registered; + return $this; + } + + public function getAttendeesRegistered(): int + { + return $this->attendees_registered; + } } diff --git a/backend/app/DomainObjects/Generated/MessageDomainObjectAbstract.php b/backend/app/DomainObjects/Generated/MessageDomainObjectAbstract.php index c496d4ac0a..acb847efb0 100644 --- a/backend/app/DomainObjects/Generated/MessageDomainObjectAbstract.php +++ b/backend/app/DomainObjects/Generated/MessageDomainObjectAbstract.php @@ -19,7 +19,7 @@ abstract class MessageDomainObjectAbstract extends \HiEvents\DomainObjects\Abstr final public const RECIPIENT_IDS = 'recipient_ids'; final public const SENT_AT = 'sent_at'; final public const ATTENDEE_IDS = 'attendee_ids'; - final public const TICKET_IDS = 'ticket_ids'; + final public const PRODUCT_IDS = 'product_ids'; final public const ORDER_ID = 'order_id'; final public const STATUS = 'status'; final public const SEND_DATA = 'send_data'; @@ -36,7 +36,7 @@ abstract class MessageDomainObjectAbstract extends \HiEvents\DomainObjects\Abstr protected array|string|null $recipient_ids = null; protected ?string $sent_at = null; protected array|string|null $attendee_ids = null; - protected array|string|null $ticket_ids = null; + protected array|string|null $product_ids = null; protected ?int $order_id = null; protected string $status; protected array|string|null $send_data = null; @@ -56,7 +56,7 @@ public function toArray(): array 'recipient_ids' => $this->recipient_ids ?? null, 'sent_at' => $this->sent_at ?? null, 'attendee_ids' => $this->attendee_ids ?? null, - 'ticket_ids' => $this->ticket_ids ?? null, + 'product_ids' => $this->product_ids ?? null, 'order_id' => $this->order_id ?? null, 'status' => $this->status ?? null, 'send_data' => $this->send_data ?? null, @@ -165,15 +165,15 @@ public function getAttendeeIds(): array|string|null return $this->attendee_ids; } - public function setTicketIds(array|string|null $ticket_ids): self + public function setProductIds(array|string|null $product_ids): self { - $this->ticket_ids = $ticket_ids; + $this->product_ids = $product_ids; return $this; } - public function getTicketIds(): array|string|null + public function getProductIds(): array|string|null { - return $this->ticket_ids; + return $this->product_ids; } public function setOrderId(?int $order_id): self diff --git a/backend/app/DomainObjects/Generated/OrderItemDomainObjectAbstract.php b/backend/app/DomainObjects/Generated/OrderItemDomainObjectAbstract.php index 71936c1922..076da8954c 100644 --- a/backend/app/DomainObjects/Generated/OrderItemDomainObjectAbstract.php +++ b/backend/app/DomainObjects/Generated/OrderItemDomainObjectAbstract.php @@ -12,8 +12,8 @@ abstract class OrderItemDomainObjectAbstract extends \HiEvents\DomainObjects\Abs final public const PLURAL_NAME = 'order_items'; final public const ID = 'id'; final public const ORDER_ID = 'order_id'; - final public const TICKET_ID = 'ticket_id'; - final public const TICKET_PRICE_ID = 'ticket_price_id'; + final public const PRODUCT_ID = 'product_id'; + final public const PRODUCT_PRICE_ID = 'product_price_id'; final public const TOTAL_BEFORE_ADDITIONS = 'total_before_additions'; final public const QUANTITY = 'quantity'; final public const ITEM_NAME = 'item_name'; @@ -24,11 +24,12 @@ abstract class OrderItemDomainObjectAbstract extends \HiEvents\DomainObjects\Abs final public const TOTAL_GROSS = 'total_gross'; final public const TOTAL_SERVICE_FEE = 'total_service_fee'; final public const TAXES_AND_FEES_ROLLUP = 'taxes_and_fees_rollup'; + final public const PRODUCT_TYPE = 'product_type'; protected int $id; protected int $order_id; - protected int $ticket_id; - protected int $ticket_price_id; + protected int $product_id; + protected int $product_price_id; protected float $total_before_additions; protected int $quantity; protected ?string $item_name = null; @@ -39,14 +40,15 @@ abstract class OrderItemDomainObjectAbstract extends \HiEvents\DomainObjects\Abs protected ?float $total_gross = null; protected ?float $total_service_fee = 0.0; protected array|string|null $taxes_and_fees_rollup = null; + protected string $product_type = 'TICKET'; public function toArray(): array { return [ 'id' => $this->id ?? null, 'order_id' => $this->order_id ?? null, - 'ticket_id' => $this->ticket_id ?? null, - 'ticket_price_id' => $this->ticket_price_id ?? null, + 'product_id' => $this->product_id ?? null, + 'product_price_id' => $this->product_price_id ?? null, 'total_before_additions' => $this->total_before_additions ?? null, 'quantity' => $this->quantity ?? null, 'item_name' => $this->item_name ?? null, @@ -57,6 +59,7 @@ public function toArray(): array 'total_gross' => $this->total_gross ?? null, 'total_service_fee' => $this->total_service_fee ?? null, 'taxes_and_fees_rollup' => $this->taxes_and_fees_rollup ?? null, + 'product_type' => $this->product_type ?? null, ]; } @@ -82,26 +85,26 @@ public function getOrderId(): int return $this->order_id; } - public function setTicketId(int $ticket_id): self + public function setProductId(int $product_id): self { - $this->ticket_id = $ticket_id; + $this->product_id = $product_id; return $this; } - public function getTicketId(): int + public function getProductId(): int { - return $this->ticket_id; + return $this->product_id; } - public function setTicketPriceId(int $ticket_price_id): self + public function setProductPriceId(int $product_price_id): self { - $this->ticket_price_id = $ticket_price_id; + $this->product_price_id = $product_price_id; return $this; } - public function getTicketPriceId(): int + public function getProductPriceId(): int { - return $this->ticket_price_id; + return $this->product_price_id; } public function setTotalBeforeAdditions(float $total_before_additions): self @@ -213,4 +216,15 @@ public function getTaxesAndFeesRollup(): array|string|null { return $this->taxes_and_fees_rollup; } + + public function setProductType(string $product_type): self + { + $this->product_type = $product_type; + return $this; + } + + public function getProductType(): string + { + return $this->product_type; + } } diff --git a/backend/app/DomainObjects/Generated/TicketCapacityAssignmentDomainObjectAbstract.php b/backend/app/DomainObjects/Generated/ProductCapacityAssignmentDomainObjectAbstract.php similarity index 79% rename from backend/app/DomainObjects/Generated/TicketCapacityAssignmentDomainObjectAbstract.php rename to backend/app/DomainObjects/Generated/ProductCapacityAssignmentDomainObjectAbstract.php index 40ed859361..048264f454 100644 --- a/backend/app/DomainObjects/Generated/TicketCapacityAssignmentDomainObjectAbstract.php +++ b/backend/app/DomainObjects/Generated/ProductCapacityAssignmentDomainObjectAbstract.php @@ -6,19 +6,19 @@ * THIS FILE IS AUTOGENERATED - DO NOT EDIT IT DIRECTLY. * @package HiEvents\DomainObjects\Generated */ -abstract class TicketCapacityAssignmentDomainObjectAbstract extends \HiEvents\DomainObjects\AbstractDomainObject +abstract class ProductCapacityAssignmentDomainObjectAbstract extends \HiEvents\DomainObjects\AbstractDomainObject { - final public const SINGULAR_NAME = 'ticket_capacity_assignment'; - final public const PLURAL_NAME = 'ticket_capacity_assignments'; + final public const SINGULAR_NAME = 'product_capacity_assignment'; + final public const PLURAL_NAME = 'product_capacity_assignments'; final public const ID = 'id'; - final public const TICKET_ID = 'ticket_id'; + final public const PRODUCT_ID = 'product_id'; final public const CAPACITY_ASSIGNMENT_ID = 'capacity_assignment_id'; final public const CREATED_AT = 'created_at'; final public const UPDATED_AT = 'updated_at'; final public const DELETED_AT = 'deleted_at'; protected int $id; - protected int $ticket_id; + protected int $product_id; protected int $capacity_assignment_id; protected ?string $created_at = null; protected ?string $updated_at = null; @@ -28,7 +28,7 @@ public function toArray(): array { return [ 'id' => $this->id ?? null, - 'ticket_id' => $this->ticket_id ?? null, + 'product_id' => $this->product_id ?? null, 'capacity_assignment_id' => $this->capacity_assignment_id ?? null, 'created_at' => $this->created_at ?? null, 'updated_at' => $this->updated_at ?? null, @@ -47,15 +47,15 @@ public function getId(): int return $this->id; } - public function setTicketId(int $ticket_id): self + public function setProductId(int $product_id): self { - $this->ticket_id = $ticket_id; + $this->product_id = $product_id; return $this; } - public function getTicketId(): int + public function getProductId(): int { - return $this->ticket_id; + return $this->product_id; } public function setCapacityAssignmentId(int $capacity_assignment_id): self diff --git a/backend/app/DomainObjects/Generated/ProductCategoryDomainObjectAbstract.php b/backend/app/DomainObjects/Generated/ProductCategoryDomainObjectAbstract.php new file mode 100644 index 0000000000..19bc308de4 --- /dev/null +++ b/backend/app/DomainObjects/Generated/ProductCategoryDomainObjectAbstract.php @@ -0,0 +1,160 @@ + $this->id ?? null, + 'event_id' => $this->event_id ?? null, + 'name' => $this->name ?? null, + 'no_products_message' => $this->no_products_message ?? null, + 'description' => $this->description ?? null, + 'is_hidden' => $this->is_hidden ?? null, + 'order' => $this->order ?? null, + 'created_at' => $this->created_at ?? null, + 'updated_at' => $this->updated_at ?? null, + 'deleted_at' => $this->deleted_at ?? null, + ]; + } + + public function setId(int $id): self + { + $this->id = $id; + return $this; + } + + public function getId(): int + { + return $this->id; + } + + public function setEventId(int $event_id): self + { + $this->event_id = $event_id; + return $this; + } + + public function getEventId(): int + { + return $this->event_id; + } + + public function setName(string $name): self + { + $this->name = $name; + return $this; + } + + public function getName(): string + { + return $this->name; + } + + public function setNoProductsMessage(?string $no_products_message): self + { + $this->no_products_message = $no_products_message; + return $this; + } + + public function getNoProductsMessage(): ?string + { + return $this->no_products_message; + } + + public function setDescription(?string $description): self + { + $this->description = $description; + return $this; + } + + public function getDescription(): ?string + { + return $this->description; + } + + public function setIsHidden(bool $is_hidden): self + { + $this->is_hidden = $is_hidden; + return $this; + } + + public function getIsHidden(): bool + { + return $this->is_hidden; + } + + public function setOrder(int $order): self + { + $this->order = $order; + return $this; + } + + public function getOrder(): int + { + return $this->order; + } + + public function setCreatedAt(?string $created_at): self + { + $this->created_at = $created_at; + return $this; + } + + public function getCreatedAt(): ?string + { + return $this->created_at; + } + + public function setUpdatedAt(?string $updated_at): self + { + $this->updated_at = $updated_at; + return $this; + } + + public function getUpdatedAt(): ?string + { + return $this->updated_at; + } + + public function setDeletedAt(?string $deleted_at): self + { + $this->deleted_at = $deleted_at; + return $this; + } + + public function getDeletedAt(): ?string + { + return $this->deleted_at; + } +} diff --git a/backend/app/DomainObjects/Generated/TicketsCheckInListDomainObjectAbstract.php b/backend/app/DomainObjects/Generated/ProductCheckInListDomainObjectAbstract.php similarity index 73% rename from backend/app/DomainObjects/Generated/TicketsCheckInListDomainObjectAbstract.php rename to backend/app/DomainObjects/Generated/ProductCheckInListDomainObjectAbstract.php index da5209eae2..427251d755 100644 --- a/backend/app/DomainObjects/Generated/TicketsCheckInListDomainObjectAbstract.php +++ b/backend/app/DomainObjects/Generated/ProductCheckInListDomainObjectAbstract.php @@ -6,17 +6,17 @@ * THIS FILE IS AUTOGENERATED - DO NOT EDIT IT DIRECTLY. * @package HiEvents\DomainObjects\Generated */ -abstract class TicketsCheckInListDomainObjectAbstract extends \HiEvents\DomainObjects\AbstractDomainObject +abstract class ProductCheckInListDomainObjectAbstract extends \HiEvents\DomainObjects\AbstractDomainObject { - final public const SINGULAR_NAME = 'tickets_check_in_list'; - final public const PLURAL_NAME = 'tickets_check_in_lists'; + final public const SINGULAR_NAME = 'product_check_in_list'; + final public const PLURAL_NAME = 'product_check_in_lists'; final public const ID = 'id'; - final public const TICKET_ID = 'ticket_id'; + final public const PRODUCT_ID = 'product_id'; final public const CHECK_IN_LIST_ID = 'check_in_list_id'; final public const DELETED_AT = 'deleted_at'; protected int $id; - protected int $ticket_id; + protected int $product_id; protected int $check_in_list_id; protected ?string $deleted_at = null; @@ -24,7 +24,7 @@ public function toArray(): array { return [ 'id' => $this->id ?? null, - 'ticket_id' => $this->ticket_id ?? null, + 'product_id' => $this->product_id ?? null, 'check_in_list_id' => $this->check_in_list_id ?? null, 'deleted_at' => $this->deleted_at ?? null, ]; @@ -41,15 +41,15 @@ public function getId(): int return $this->id; } - public function setTicketId(int $ticket_id): self + public function setProductId(int $product_id): self { - $this->ticket_id = $ticket_id; + $this->product_id = $product_id; return $this; } - public function getTicketId(): int + public function getProductId(): int { - return $this->ticket_id; + return $this->product_id; } public function setCheckInListId(int $check_in_list_id): self diff --git a/backend/app/DomainObjects/Generated/TicketDomainObjectAbstract.php b/backend/app/DomainObjects/Generated/ProductDomainObjectAbstract.php similarity index 89% rename from backend/app/DomainObjects/Generated/TicketDomainObjectAbstract.php rename to backend/app/DomainObjects/Generated/ProductDomainObjectAbstract.php index 691ce41e52..ca6e0c4f24 100644 --- a/backend/app/DomainObjects/Generated/TicketDomainObjectAbstract.php +++ b/backend/app/DomainObjects/Generated/ProductDomainObjectAbstract.php @@ -6,12 +6,13 @@ * THIS FILE IS AUTOGENERATED - DO NOT EDIT IT DIRECTLY. * @package HiEvents\DomainObjects\Generated */ -abstract class TicketDomainObjectAbstract extends \HiEvents\DomainObjects\AbstractDomainObject +abstract class ProductDomainObjectAbstract extends \HiEvents\DomainObjects\AbstractDomainObject { - final public const SINGULAR_NAME = 'ticket'; - final public const PLURAL_NAME = 'tickets'; + final public const SINGULAR_NAME = 'product'; + final public const PLURAL_NAME = 'products'; final public const ID = 'id'; final public const EVENT_ID = 'event_id'; + final public const PRODUCT_CATEGORY_ID = 'product_category_id'; final public const TITLE = 'title'; final public const SALE_START_DATE = 'sale_start_date'; final public const SALE_END_DATE = 'sale_end_date'; @@ -32,9 +33,11 @@ abstract class TicketDomainObjectAbstract extends \HiEvents\DomainObjects\Abstra final public const TYPE = 'type'; final public const IS_HIDDEN = 'is_hidden'; final public const START_COLLAPSED = 'start_collapsed'; + final public const PRODUCT_TYPE = 'product_type'; protected int $id; protected int $event_id; + protected ?int $product_category_id = null; protected string $title; protected ?string $sale_start_date = null; protected ?string $sale_end_date = null; @@ -55,12 +58,14 @@ abstract class TicketDomainObjectAbstract extends \HiEvents\DomainObjects\Abstra protected string $type = 'PAID'; protected ?bool $is_hidden = false; protected bool $start_collapsed = false; + protected string $product_type = 'TICKET'; public function toArray(): array { return [ 'id' => $this->id ?? null, 'event_id' => $this->event_id ?? null, + 'product_category_id' => $this->product_category_id ?? null, 'title' => $this->title ?? null, 'sale_start_date' => $this->sale_start_date ?? null, 'sale_end_date' => $this->sale_end_date ?? null, @@ -81,6 +86,7 @@ public function toArray(): array 'type' => $this->type ?? null, 'is_hidden' => $this->is_hidden ?? null, 'start_collapsed' => $this->start_collapsed ?? null, + 'product_type' => $this->product_type ?? null, ]; } @@ -106,6 +112,17 @@ public function getEventId(): int return $this->event_id; } + public function setProductCategoryId(?int $product_category_id): self + { + $this->product_category_id = $product_category_id; + return $this; + } + + public function getProductCategoryId(): ?int + { + return $this->product_category_id; + } + public function setTitle(string $title): self { $this->title = $title; @@ -325,4 +342,15 @@ public function getStartCollapsed(): bool { return $this->start_collapsed; } + + public function setProductType(string $product_type): self + { + $this->product_type = $product_type; + return $this; + } + + public function getProductType(): string + { + return $this->product_type; + } } diff --git a/backend/app/DomainObjects/Generated/TicketPriceDomainObjectAbstract.php b/backend/app/DomainObjects/Generated/ProductPriceDomainObjectAbstract.php similarity index 90% rename from backend/app/DomainObjects/Generated/TicketPriceDomainObjectAbstract.php rename to backend/app/DomainObjects/Generated/ProductPriceDomainObjectAbstract.php index c6e47272c4..ee1f14577a 100644 --- a/backend/app/DomainObjects/Generated/TicketPriceDomainObjectAbstract.php +++ b/backend/app/DomainObjects/Generated/ProductPriceDomainObjectAbstract.php @@ -6,12 +6,12 @@ * THIS FILE IS AUTOGENERATED - DO NOT EDIT IT DIRECTLY. * @package HiEvents\DomainObjects\Generated */ -abstract class TicketPriceDomainObjectAbstract extends \HiEvents\DomainObjects\AbstractDomainObject +abstract class ProductPriceDomainObjectAbstract extends \HiEvents\DomainObjects\AbstractDomainObject { - final public const SINGULAR_NAME = 'ticket_price'; - final public const PLURAL_NAME = 'ticket_prices'; + final public const SINGULAR_NAME = 'product_price'; + final public const PLURAL_NAME = 'product_prices'; final public const ID = 'id'; - final public const TICKET_ID = 'ticket_id'; + final public const PRODUCT_ID = 'product_id'; final public const PRICE = 'price'; final public const LABEL = 'label'; final public const SALE_START_DATE = 'sale_start_date'; @@ -26,7 +26,7 @@ abstract class TicketPriceDomainObjectAbstract extends \HiEvents\DomainObjects\A final public const QUANTITY_AVAILABLE = 'quantity_available'; protected int $id; - protected int $ticket_id; + protected int $product_id; protected float $price; protected ?string $label = null; protected ?string $sale_start_date = null; @@ -44,7 +44,7 @@ public function toArray(): array { return [ 'id' => $this->id ?? null, - 'ticket_id' => $this->ticket_id ?? null, + 'product_id' => $this->product_id ?? null, 'price' => $this->price ?? null, 'label' => $this->label ?? null, 'sale_start_date' => $this->sale_start_date ?? null, @@ -71,15 +71,15 @@ public function getId(): int return $this->id; } - public function setTicketId(int $ticket_id): self + public function setProductId(int $product_id): self { - $this->ticket_id = $ticket_id; + $this->product_id = $product_id; return $this; } - public function getTicketId(): int + public function getProductId(): int { - return $this->ticket_id; + return $this->product_id; } public function setPrice(float $price): self diff --git a/backend/app/DomainObjects/Generated/TicketQuestionDomainObjectAbstract.php b/backend/app/DomainObjects/Generated/ProductQuestionDomainObjectAbstract.php similarity index 71% rename from backend/app/DomainObjects/Generated/TicketQuestionDomainObjectAbstract.php rename to backend/app/DomainObjects/Generated/ProductQuestionDomainObjectAbstract.php index 7aaba00d1c..7b3cbebfb4 100644 --- a/backend/app/DomainObjects/Generated/TicketQuestionDomainObjectAbstract.php +++ b/backend/app/DomainObjects/Generated/ProductQuestionDomainObjectAbstract.php @@ -6,17 +6,17 @@ * THIS FILE IS AUTOGENERATED - DO NOT EDIT IT DIRECTLY. * @package HiEvents\DomainObjects\Generated */ -abstract class TicketQuestionDomainObjectAbstract extends \HiEvents\DomainObjects\AbstractDomainObject +abstract class ProductQuestionDomainObjectAbstract extends \HiEvents\DomainObjects\AbstractDomainObject { - final public const SINGULAR_NAME = 'ticket_question'; - final public const PLURAL_NAME = 'ticket_questions'; + final public const SINGULAR_NAME = 'product_question'; + final public const PLURAL_NAME = 'product_questions'; final public const ID = 'id'; - final public const TICKET_ID = 'ticket_id'; + final public const PRODUCT_ID = 'product_id'; final public const QUESTION_ID = 'question_id'; final public const DELETED_AT = 'deleted_at'; protected int $id; - protected int $ticket_id; + protected int $product_id; protected int $question_id; protected ?string $deleted_at = null; @@ -24,7 +24,7 @@ public function toArray(): array { return [ 'id' => $this->id ?? null, - 'ticket_id' => $this->ticket_id ?? null, + 'product_id' => $this->product_id ?? null, 'question_id' => $this->question_id ?? null, 'deleted_at' => $this->deleted_at ?? null, ]; @@ -41,15 +41,15 @@ public function getId(): int return $this->id; } - public function setTicketId(int $ticket_id): self + public function setProductId(int $product_id): self { - $this->ticket_id = $ticket_id; + $this->product_id = $product_id; return $this; } - public function getTicketId(): int + public function getProductId(): int { - return $this->ticket_id; + return $this->product_id; } public function setQuestionId(int $question_id): self diff --git a/backend/app/DomainObjects/Generated/TicketTaxAndFeesDomainObjectAbstract.php b/backend/app/DomainObjects/Generated/ProductTaxAndFeesDomainObjectAbstract.php similarity index 64% rename from backend/app/DomainObjects/Generated/TicketTaxAndFeesDomainObjectAbstract.php rename to backend/app/DomainObjects/Generated/ProductTaxAndFeesDomainObjectAbstract.php index f4f59d977d..6c1b7840a8 100644 --- a/backend/app/DomainObjects/Generated/TicketTaxAndFeesDomainObjectAbstract.php +++ b/backend/app/DomainObjects/Generated/ProductTaxAndFeesDomainObjectAbstract.php @@ -6,23 +6,23 @@ * THIS FILE IS AUTOGENERATED - DO NOT EDIT IT DIRECTLY. * @package HiEvents\DomainObjects\Generated */ -abstract class TicketTaxAndFeesDomainObjectAbstract extends \HiEvents\DomainObjects\AbstractDomainObject +abstract class ProductTaxAndFeesDomainObjectAbstract extends \HiEvents\DomainObjects\AbstractDomainObject { - final public const SINGULAR_NAME = 'ticket_tax_and_fees'; - final public const PLURAL_NAME = 'ticket_tax_and_fees'; + final public const SINGULAR_NAME = 'product_tax_and_fees'; + final public const PLURAL_NAME = 'product_tax_and_fees'; final public const ID = 'id'; - final public const TICKET_ID = 'ticket_id'; + final public const PRODUCT_ID = 'product_id'; final public const TAX_AND_FEE_ID = 'tax_and_fee_id'; protected int $id; - protected int $ticket_id; + protected int $product_id; protected int $tax_and_fee_id; public function toArray(): array { return [ 'id' => $this->id ?? null, - 'ticket_id' => $this->ticket_id ?? null, + 'product_id' => $this->product_id ?? null, 'tax_and_fee_id' => $this->tax_and_fee_id ?? null, ]; } @@ -38,15 +38,15 @@ public function getId(): int return $this->id; } - public function setTicketId(int $ticket_id): self + public function setProductId(int $product_id): self { - $this->ticket_id = $ticket_id; + $this->product_id = $product_id; return $this; } - public function getTicketId(): int + public function getProductId(): int { - return $this->ticket_id; + return $this->product_id; } public function setTaxAndFeeId(int $tax_and_fee_id): self diff --git a/backend/app/DomainObjects/Generated/PromoCodeDomainObjectAbstract.php b/backend/app/DomainObjects/Generated/PromoCodeDomainObjectAbstract.php index f4bda2b518..c1e7174e18 100644 --- a/backend/app/DomainObjects/Generated/PromoCodeDomainObjectAbstract.php +++ b/backend/app/DomainObjects/Generated/PromoCodeDomainObjectAbstract.php @@ -14,7 +14,7 @@ abstract class PromoCodeDomainObjectAbstract extends \HiEvents\DomainObjects\Abs final public const EVENT_ID = 'event_id'; final public const CODE = 'code'; final public const DISCOUNT = 'discount'; - final public const APPLICABLE_TICKET_IDS = 'applicable_ticket_ids'; + final public const APPLICABLE_PRODUCT_IDS = 'applicable_product_ids'; final public const EXPIRY_DATE = 'expiry_date'; final public const DISCOUNT_TYPE = 'discount_type'; final public const ATTENDEE_USAGE_COUNT = 'attendee_usage_count'; @@ -28,7 +28,7 @@ abstract class PromoCodeDomainObjectAbstract extends \HiEvents\DomainObjects\Abs protected int $event_id; protected string $code; protected float $discount = 0.0; - protected array|string|null $applicable_ticket_ids = null; + protected array|string|null $applicable_product_ids = null; protected ?string $expiry_date = null; protected ?string $discount_type = null; protected int $attendee_usage_count = 0; @@ -45,7 +45,7 @@ public function toArray(): array 'event_id' => $this->event_id ?? null, 'code' => $this->code ?? null, 'discount' => $this->discount ?? null, - 'applicable_ticket_ids' => $this->applicable_ticket_ids ?? null, + 'applicable_product_ids' => $this->applicable_product_ids ?? null, 'expiry_date' => $this->expiry_date ?? null, 'discount_type' => $this->discount_type ?? null, 'attendee_usage_count' => $this->attendee_usage_count ?? null, @@ -101,15 +101,15 @@ public function getDiscount(): float return $this->discount; } - public function setApplicableTicketIds(array|string|null $applicable_ticket_ids): self + public function setApplicableProductIds(array|string|null $applicable_product_ids): self { - $this->applicable_ticket_ids = $applicable_ticket_ids; + $this->applicable_product_ids = $applicable_product_ids; return $this; } - public function getApplicableTicketIds(): array|string|null + public function getApplicableProductIds(): array|string|null { - return $this->applicable_ticket_ids; + return $this->applicable_product_ids; } public function setExpiryDate(?string $expiry_date): self diff --git a/backend/app/DomainObjects/Generated/QuestionAnswerDomainObjectAbstract.php b/backend/app/DomainObjects/Generated/QuestionAnswerDomainObjectAbstract.php index f71fc5ae13..0505f94c3e 100644 --- a/backend/app/DomainObjects/Generated/QuestionAnswerDomainObjectAbstract.php +++ b/backend/app/DomainObjects/Generated/QuestionAnswerDomainObjectAbstract.php @@ -14,7 +14,7 @@ abstract class QuestionAnswerDomainObjectAbstract extends \HiEvents\DomainObject final public const QUESTION_ID = 'question_id'; final public const ORDER_ID = 'order_id'; final public const ATTENDEE_ID = 'attendee_id'; - final public const TICKET_ID = 'ticket_id'; + final public const PRODUCT_ID = 'product_id'; final public const CREATED_AT = 'created_at'; final public const UPDATED_AT = 'updated_at'; final public const DELETED_AT = 'deleted_at'; @@ -24,7 +24,7 @@ abstract class QuestionAnswerDomainObjectAbstract extends \HiEvents\DomainObject protected int $question_id; protected int $order_id; protected ?int $attendee_id = null; - protected ?int $ticket_id = null; + protected ?int $product_id = null; protected string $created_at; protected string $updated_at; protected ?string $deleted_at = null; @@ -37,7 +37,7 @@ public function toArray(): array 'question_id' => $this->question_id ?? null, 'order_id' => $this->order_id ?? null, 'attendee_id' => $this->attendee_id ?? null, - 'ticket_id' => $this->ticket_id ?? null, + 'product_id' => $this->product_id ?? null, 'created_at' => $this->created_at ?? null, 'updated_at' => $this->updated_at ?? null, 'deleted_at' => $this->deleted_at ?? null, @@ -89,15 +89,15 @@ public function getAttendeeId(): ?int return $this->attendee_id; } - public function setTicketId(?int $ticket_id): self + public function setProductId(?int $product_id): self { - $this->ticket_id = $ticket_id; + $this->product_id = $product_id; return $this; } - public function getTicketId(): ?int + public function getProductId(): ?int { - return $this->ticket_id; + return $this->product_id; } public function setCreatedAt(string $created_at): self diff --git a/backend/app/DomainObjects/Generated/TicketCheckInListDomainObjectAbstract.php b/backend/app/DomainObjects/Generated/TicketCheckInListDomainObjectAbstract.php deleted file mode 100644 index 6b4722ada8..0000000000 --- a/backend/app/DomainObjects/Generated/TicketCheckInListDomainObjectAbstract.php +++ /dev/null @@ -1,76 +0,0 @@ - $this->id ?? null, - 'ticket_id' => $this->ticket_id ?? null, - 'check_in_list_id' => $this->check_in_list_id ?? null, - 'deleted_at' => $this->deleted_at ?? null, - ]; - } - - public function setId(int $id): self - { - $this->id = $id; - return $this; - } - - public function getId(): int - { - return $this->id; - } - - public function setTicketId(int $ticket_id): self - { - $this->ticket_id = $ticket_id; - return $this; - } - - public function getTicketId(): int - { - return $this->ticket_id; - } - - public function setCheckInListId(int $check_in_list_id): self - { - $this->check_in_list_id = $check_in_list_id; - return $this; - } - - public function getCheckInListId(): int - { - return $this->check_in_list_id; - } - - public function setDeletedAt(?string $deleted_at): self - { - $this->deleted_at = $deleted_at; - return $this; - } - - public function getDeletedAt(): ?string - { - return $this->deleted_at; - } -} diff --git a/backend/app/DomainObjects/Generated/TicketTaxDomainObjectAbstract.php b/backend/app/DomainObjects/Generated/TicketTaxDomainObjectAbstract.php deleted file mode 100644 index 66295f559e..0000000000 --- a/backend/app/DomainObjects/Generated/TicketTaxDomainObjectAbstract.php +++ /dev/null @@ -1,62 +0,0 @@ - $this->id ?? null, - 'ticket_id' => $this->ticket_id ?? null, - 'tax_id' => $this->tax_id ?? null, - ]; - } - - public function setId(int $id): self - { - $this->id = $id; - return $this; - } - - public function getId(): int - { - return $this->id; - } - - public function setTicketId(int $ticket_id): self - { - $this->ticket_id = $ticket_id; - return $this; - } - - public function getTicketId(): int - { - return $this->ticket_id; - } - - public function setTaxId(int $tax_id): self - { - $this->tax_id = $tax_id; - return $this; - } - - public function getTaxId(): int - { - return $this->tax_id; - } -} diff --git a/backend/app/DomainObjects/OrderDomainObject.php b/backend/app/DomainObjects/OrderDomainObject.php index eb6bdd60ee..b196bb5e72 100644 --- a/backend/app/DomainObjects/OrderDomainObject.php +++ b/backend/app/DomainObjects/OrderDomainObject.php @@ -2,6 +2,7 @@ namespace HiEvents\DomainObjects; +use HiEvents\DomainObjects\Enums\ProductType; use HiEvents\DomainObjects\Interfaces\IsSortable; use HiEvents\DomainObjects\SortingAndFiltering\AllowedSorts; use HiEvents\DomainObjects\Status\OrderPaymentStatus; @@ -66,6 +67,28 @@ public function getFullName(): string return $this->getFirstName() . ' ' . $this->getLastName(); } + public function getProductOrderItems(): Collection + { + if ($this->getOrderItems() === null) { + return new Collection(); + } + + return $this->getOrderItems()->filter(static function (OrderItemDomainObject $orderItem) { + return $orderItem->getProductType() === ProductType::GENERAL->name; + }); + } + + public function getTicketOrderItems(): Collection + { + if ($this->getOrderItems() === null) { + return new Collection(); + } + + return $this->getOrderItems()->filter(static function (OrderItemDomainObject $orderItem) { + return $orderItem->getProductType() === ProductType::TICKET->name; + }); + } + public function setOrderItems(?Collection $orderItems): OrderDomainObject { $this->orderItems = $orderItems; @@ -124,7 +147,7 @@ public function isPartiallyRefunded(): bool public function isFullyRefunded(): bool { - return $this->getTotalRefunded() >= $this->getTotalGross(); + return !$this->isFreeOrder() && ($this->getTotalRefunded() >= $this->getTotalGross()); } public function getStripePayment(): ?StripePaymentDomainObject diff --git a/backend/app/DomainObjects/OrderItemDomainObject.php b/backend/app/DomainObjects/OrderItemDomainObject.php index 1a93d71e59..164b1d9c02 100644 --- a/backend/app/DomainObjects/OrderItemDomainObject.php +++ b/backend/app/DomainObjects/OrderItemDomainObject.php @@ -6,35 +6,49 @@ class OrderItemDomainObject extends Generated\OrderItemDomainObjectAbstract { - private ?TicketPriceDomainObject $ticketPrice = null; + private ?ProductPriceDomainObject $productPrice = null; - public ?TicketDomainObject $ticket = null; + public ?ProductDomainObject $product = null; + + public ?OrderDomainObject $order = null; public function getTotalBeforeDiscount(): float { return Currency::round($this->getPriceBeforeDiscount() * $this->getQuantity()); } - public function getTicketPrice(): ?TicketPriceDomainObject + public function getProductPrice(): ?ProductPriceDomainObject + { + return $this->productPrice; + } + + public function setProductPrice(?ProductPriceDomainObject $tier): self + { + $this->productPrice = $tier; + + return $this; + } + + public function getProduct(): ?ProductDomainObject { - return $this->ticketPrice; + return $this->product; } - public function setTicketPrice(?TicketPriceDomainObject $tier): self + public function setProduct(?ProductDomainObject $product): self { - $this->ticketPrice = $tier; + $this->product = $product; return $this; } - public function getTicket(): ?TicketDomainObject + public function getOrder(): ?OrderDomainObject { - return $this->ticket; + return $this->order; } - public function setTicket(?TicketDomainObject $ticket): self + public function setOrder(?OrderDomainObject $order): self { - $this->ticket = $ticket; + $this->order = $order; return $this; } diff --git a/backend/app/DomainObjects/ProductCapacityAssignmentDomainObject.php b/backend/app/DomainObjects/ProductCapacityAssignmentDomainObject.php new file mode 100644 index 0000000000..6560231b64 --- /dev/null +++ b/backend/app/DomainObjects/ProductCapacityAssignmentDomainObject.php @@ -0,0 +1,7 @@ +products = $products; + } + + public function getProducts(): ?Collection + { + return $this->products; + } +} diff --git a/backend/app/DomainObjects/TicketsCheckInListDomainObject.php b/backend/app/DomainObjects/ProductCheckInListDomainObject.php similarity index 54% rename from backend/app/DomainObjects/TicketsCheckInListDomainObject.php rename to backend/app/DomainObjects/ProductCheckInListDomainObject.php index 9e10d8b012..77ef459829 100644 --- a/backend/app/DomainObjects/TicketsCheckInListDomainObject.php +++ b/backend/app/DomainObjects/ProductCheckInListDomainObject.php @@ -2,6 +2,6 @@ namespace HiEvents\DomainObjects; -class TicketsCheckInListDomainObject extends Generated\TicketsCheckInListDomainObjectAbstract +class ProductCheckInListDomainObject extends Generated\ProductCheckInListDomainObjectAbstract { } diff --git a/backend/app/DomainObjects/TicketDomainObject.php b/backend/app/DomainObjects/ProductDomainObject.php similarity index 66% rename from backend/app/DomainObjects/TicketDomainObject.php rename to backend/app/DomainObjects/ProductDomainObject.php index 718a5cc656..bf54e4f81d 100644 --- a/backend/app/DomainObjects/TicketDomainObject.php +++ b/backend/app/DomainObjects/ProductDomainObject.php @@ -3,14 +3,14 @@ namespace HiEvents\DomainObjects; use Carbon\Carbon; +use HiEvents\DomainObjects\Enums\ProductPriceType; use HiEvents\Constants; -use HiEvents\DomainObjects\Enums\TicketType; use HiEvents\DomainObjects\Interfaces\IsSortable; use HiEvents\DomainObjects\SortingAndFiltering\AllowedSorts; use Illuminate\Support\Collection; use LogicException; -class TicketDomainObject extends Generated\TicketDomainObjectAbstract implements IsSortable +class ProductDomainObject extends Generated\ProductDomainObjectAbstract implements IsSortable { private ?Collection $taxAndFees = null; @@ -57,7 +57,7 @@ public static function getAllowedSorts(): AllowedSorts ); } - public function setTaxAndFees(Collection $taxes): TicketDomainObject + public function setTaxAndFees(Collection $taxes): ProductDomainObject { $this->taxAndFees = $taxes; return $this; @@ -80,36 +80,36 @@ public function getFees(): ?Collection public function isSoldOut(): bool { - if (!$this->getTicketPrices() || $this->getTicketPrices()->isEmpty()) { + if (!$this->getProductPrices() || $this->getProductPrices()->isEmpty()) { return true; } - return $this->getTicketPrices()->every(fn(TicketPriceDomainObject $price) => $price->isSoldOut()); + return $this->getProductPrices()->every(fn(ProductPriceDomainObject $price) => $price->isSoldOut()); } public function getQuantityAvailable(): int { - $availableCount = $this->getTicketPrices()->sum(fn(TicketPriceDomainObject $price) => $price->getQuantityAvailable()); + $availableCount = $this->getProductPrices()->sum(fn(ProductPriceDomainObject $price) => $price->getQuantityAvailable()); if ($this->quantityAvailable !== null) { return min($availableCount, $this->quantityAvailable); } - if (!$this->getTicketPrices() || $this->getTicketPrices()->isEmpty()) { + if (!$this->getProductPrices() || $this->getProductPrices()->isEmpty()) { return 0; } // This is to address a case where prices have an unlimited quantity available and the user has // enabled show_quantity_remaining. if ($this->getShowQuantityRemaining() - && $this->getTicketPrices()->first(fn(TicketPriceDomainObject $price) => $price->getQuantityAvailable() === null)) { + && $this->getProductPrices()->first(fn(ProductPriceDomainObject $price) => $price->getQuantityAvailable() === null)) { return Constants::INFINITE; } return $availableCount; } - public function setQuantityAvailable(int $quantityAvailable): TicketDomainObject + public function setQuantityAvailable(int $quantityAvailable): ProductDomainObject { $this->quantityAvailable = $quantityAvailable; @@ -133,7 +133,7 @@ public function isAfterSaleEndDate(): bool public function isAvailable(): bool { // If all prices are hidden, it's not available - if ($this->getType() === TicketType::TIERED->name && $this->getTicketPrices()?->isEmpty()) { + if ($this->getType() === ProductPriceType::TIERED->name && $this->getProductPrices()?->isEmpty()) { return false; } @@ -144,14 +144,14 @@ public function isAvailable(): bool } /** - * @return Collection|null + * @return Collection|null */ - public function getTicketPrices(): ?Collection + public function getProductPrices(): ?Collection { return $this->prices; } - public function setTicketPrices(?Collection $prices): self + public function setProductPrices(?Collection $prices): self { $this->prices = $prices; @@ -159,59 +159,59 @@ public function setTicketPrices(?Collection $prices): self } /** - * All ticket types except TIERED have a single price, so we can just return the first price. + * All product types except TIERED have a single price, so we can just return the first price. * * @return float|null */ public function getPrice(): ?float { - if ($this->getType() === TicketType::TIERED->name) { - throw new LogicException('You cannot get a single price for a tiered ticket. Use getPrices() instead.'); + if ($this->getType() === ProductPriceType::TIERED->name) { + throw new LogicException('You cannot get a single price for a tiered product. Use getPrices() instead.'); } - return $this->getTicketPrices()?->first()->getPrice(); + return $this->getProductPrices()?->first()->getPrice(); } - public function getPriceById(int $priceId): ?TicketPriceDomainObject + public function getPriceById(int $priceId): ?ProductPriceDomainObject { - return $this->getTicketPrices()?->first(fn(TicketPriceDomainObject $price) => $price->getId() === $priceId); + return $this->getProductPrices()?->first(fn(ProductPriceDomainObject $price) => $price->getId() === $priceId); } public function isTieredType(): bool { - return $this->getType() === TicketType::TIERED->name; + return $this->getType() === ProductPriceType::TIERED->name; } public function isDonationType(): bool { - return $this->getType() === TicketType::DONATION->name; + return $this->getType() === ProductPriceType::DONATION->name; } public function isPaidType(): bool { - return $this->getType() === TicketType::PAID->name; + return $this->getType() === ProductPriceType::PAID->name; } public function isFreeType(): bool { - return $this->getType() === TicketType::FREE->name; + return $this->getType() === ProductPriceType::FREE->name; } public function getInitialQuantityAvailable(): ?int { - if ($this->getType() === TicketType::TIERED->name) { - return $this->getTicketPrices()?->sum(fn(TicketPriceDomainObject $price) => $price->getInitialQuantityAvailable()); + if ($this->getType() === ProductPriceType::TIERED->name) { + return $this->getProductPrices()?->sum(fn(ProductPriceDomainObject $price) => $price->getInitialQuantityAvailable()); } - return $this->getTicketPrices()?->first()?->getInitialQuantityAvailable(); + return $this->getProductPrices()?->first()?->getInitialQuantityAvailable(); } public function getQuantitySold(): int { - return $this->getTicketPrices()?->sum(fn(TicketPriceDomainObject $price) => $price->getQuantitySold()) ?? 0; + return $this->getProductPrices()?->sum(fn(ProductPriceDomainObject $price) => $price->getQuantitySold()) ?? 0; } - public function setOffSaleReason(?string $offSaleReason): TicketDomainObject + public function setOffSaleReason(?string $offSaleReason): ProductDomainObject { $this->offSaleReason = $offSaleReason; diff --git a/backend/app/DomainObjects/TicketPriceDomainObject.php b/backend/app/DomainObjects/ProductPriceDomainObject.php similarity index 89% rename from backend/app/DomainObjects/TicketPriceDomainObject.php rename to backend/app/DomainObjects/ProductPriceDomainObject.php index d2d312e96f..0919781ec1 100644 --- a/backend/app/DomainObjects/TicketPriceDomainObject.php +++ b/backend/app/DomainObjects/ProductPriceDomainObject.php @@ -6,7 +6,7 @@ use HiEvents\Helper\Currency; use LogicException; -class TicketPriceDomainObject extends Generated\TicketPriceDomainObjectAbstract +class ProductPriceDomainObject extends Generated\ProductPriceDomainObjectAbstract { private ?float $priceBeforeDiscount = null; @@ -23,7 +23,7 @@ public function getPriceBeforeDiscount(): ?float return $this->priceBeforeDiscount; } - public function setPriceBeforeDiscount(?float $originalPrice): TicketPriceDomainObject + public function setPriceBeforeDiscount(?float $originalPrice): ProductPriceDomainObject { $this->priceBeforeDiscount = $originalPrice; @@ -96,13 +96,13 @@ public function isAvailable(): ?bool return $this->isAvailable; } - public function setIsAvailable(?bool $isAvailable): TicketPriceDomainObject + public function setIsAvailable(?bool $isAvailable): ProductPriceDomainObject { $this->isAvailable = $isAvailable; return $this; } - public function setOffSaleReason(?string $offSaleReason): TicketPriceDomainObject + public function setOffSaleReason(?string $offSaleReason): ProductPriceDomainObject { $this->offSaleReason = $offSaleReason; diff --git a/backend/app/DomainObjects/ProductQuestionDomainObject.php b/backend/app/DomainObjects/ProductQuestionDomainObject.php new file mode 100644 index 0000000000..866ff446c3 --- /dev/null +++ b/backend/app/DomainObjects/ProductQuestionDomainObject.php @@ -0,0 +1,7 @@ +getApplicableTicketIds()) { + // If there's no product IDs we apply the promo to all products + if (!$this->getApplicableProductIds()) { return true; } - return in_array($ticket->getId(), array_map('intval', $this->getApplicableTicketIds()), true); + return in_array($product->getId(), array_map('intval', $this->getApplicableProductIds()), true); } public function isFixedDiscount(): bool diff --git a/backend/app/DomainObjects/QuestionAndAnswerViewDomainObject.php b/backend/app/DomainObjects/QuestionAndAnswerViewDomainObject.php index b0af866487..41abc43f88 100644 --- a/backend/app/DomainObjects/QuestionAndAnswerViewDomainObject.php +++ b/backend/app/DomainObjects/QuestionAndAnswerViewDomainObject.php @@ -10,6 +10,8 @@ class QuestionAndAnswerViewDomainObject extends AbstractDomainObject final public const SINGULAR_NAME = 'question_and_answer_view'; final public const PLURAL_NAME = 'question_and_answer_views'; + private ?int $product_id; + private ?string $product_title; private int $question_id; private ?int $order_id; private string $title; @@ -131,6 +133,28 @@ public function setEventId(int $event_id): QuestionAndAnswerViewDomainObject return $this; } + public function getProductId(): ?int + { + return $this->product_id; + } + + public function setProductId(?int $product_id): QuestionAndAnswerViewDomainObject + { + $this->product_id = $product_id; + return $this; + } + + public function getProductTitle(): ?string + { + return $this->product_title; + } + + public function setProductTitle(?string $product_title): QuestionAndAnswerViewDomainObject + { + $this->product_title = $product_title; + return $this; + } + public function toArray(): array { return [ diff --git a/backend/app/DomainObjects/QuestionDomainObject.php b/backend/app/DomainObjects/QuestionDomainObject.php index 6ddd5a1331..5b544cc98d 100644 --- a/backend/app/DomainObjects/QuestionDomainObject.php +++ b/backend/app/DomainObjects/QuestionDomainObject.php @@ -7,24 +7,26 @@ class QuestionDomainObject extends Generated\QuestionDomainObjectAbstract { - public ?Collection $tickets = null; + public ?Collection $products = null; - public function setTickets(?Collection $tickets): QuestionDomainObject + public function setProducts(?Collection $products): QuestionDomainObject { - $this->tickets = $tickets; + $this->products = $products; return $this; } - public function getTickets(): ?Collection + public function getProducts(): ?Collection { - return $this->tickets; + return $this->products; } - public function isMultipleChoice(): bool + public function isPreDefinedChoice(): bool { return in_array($this->getType(), [ - QuestionTypeEnum::MULTI_SELECT_DROPDOWN, - QuestionTypeEnum::CHECKBOX, + QuestionTypeEnum::MULTI_SELECT_DROPDOWN->name, + QuestionTypeEnum::CHECKBOX->name, + QuestionTypeEnum::RADIO->name, + QuestionTypeEnum::DROPDOWN->name, ], true); } diff --git a/backend/app/DomainObjects/Status/TicketStatus.php b/backend/app/DomainObjects/Status/ProductStatus.php similarity index 88% rename from backend/app/DomainObjects/Status/TicketStatus.php rename to backend/app/DomainObjects/Status/ProductStatus.php index 2f6e2bfa8c..fe55a34752 100644 --- a/backend/app/DomainObjects/Status/TicketStatus.php +++ b/backend/app/DomainObjects/Status/ProductStatus.php @@ -4,7 +4,7 @@ use HiEvents\DomainObjects\Enums\BaseEnum; -enum TicketStatus +enum ProductStatus { use BaseEnum; diff --git a/backend/app/DomainObjects/TicketAttributeDomainObject.php b/backend/app/DomainObjects/TicketAttributeDomainObject.php deleted file mode 100644 index 00aeeee035..0000000000 --- a/backend/app/DomainObjects/TicketAttributeDomainObject.php +++ /dev/null @@ -1,7 +0,0 @@ -getCheckedInAt() ? Carbon::parse($attendee->getCheckedInAt())->format('Y-m-d H:i:s') : '', - $attendee->getTicketId(), + $attendee->getProductId(), $attendee->getEventId(), $attendee->getPublicId(), $attendee->getShortId(), diff --git a/backend/app/Helper/Url.php b/backend/app/Helper/Url.php index 4eb96a6e5a..aff22c5491 100644 --- a/backend/app/Helper/Url.php +++ b/backend/app/Helper/Url.php @@ -11,7 +11,7 @@ class Url public const ACCEPT_INVITATION = 'app.frontend_urls.accept_invitation'; public const CONFIRM_EMAIL_ADDRESS = 'app.frontend_urls.confirm_email_address'; public const EVENT_HOMEPAGE = 'app.frontend_urls.event_homepage'; - public const ATTENDEE_TICKET = 'app.frontend_urls.attendee_ticket'; + public const ATTENDEE_TICKET = 'app.frontend_urls.attendee_product'; public const ORDER_SUMMARY = 'app.frontend_urls.order_summary'; public const ORGANIZER_ORDER_SUMMARY = 'app.frontend_urls.organizer_order_summary'; diff --git a/backend/app/Http/Actions/Attendees/CreateAttendeeAction.php b/backend/app/Http/Actions/Attendees/CreateAttendeeAction.php index e43a97761b..34808b3661 100644 --- a/backend/app/Http/Actions/Attendees/CreateAttendeeAction.php +++ b/backend/app/Http/Actions/Attendees/CreateAttendeeAction.php @@ -3,8 +3,8 @@ namespace HiEvents\Http\Actions\Attendees; use HiEvents\DomainObjects\EventDomainObject; -use HiEvents\Exceptions\InvalidTicketPriceId; -use HiEvents\Exceptions\NoTicketsAvailableException; +use HiEvents\Exceptions\InvalidProductPriceId; +use HiEvents\Exceptions\NoProductsAvailableException; use HiEvents\Http\Actions\BaseAction; use HiEvents\Http\Request\Attendee\CreateAttendeeRequest; use HiEvents\Http\ResponseCodes; @@ -37,13 +37,13 @@ public function __invoke(CreateAttendeeRequest $request, int $eventId): JsonResp 'event_id' => $eventId, ]) )); - } catch (NoTicketsAvailableException $exception) { + } catch (NoProductsAvailableException $exception) { throw ValidationException::withMessages([ - 'ticket_id' => $exception->getMessage(), + 'product_id' => $exception->getMessage(), ]); - } catch (InvalidTicketPriceId $exception) { + } catch (InvalidProductPriceId $exception) { throw ValidationException::withMessages([ - 'ticket_price_id' => $exception->getMessage(), + 'product_price_id' => $exception->getMessage(), ]); } diff --git a/backend/app/Http/Actions/Attendees/EditAttendeeAction.php b/backend/app/Http/Actions/Attendees/EditAttendeeAction.php index c9f11f6d40..85eaf7d659 100644 --- a/backend/app/Http/Actions/Attendees/EditAttendeeAction.php +++ b/backend/app/Http/Actions/Attendees/EditAttendeeAction.php @@ -3,7 +3,7 @@ namespace HiEvents\Http\Actions\Attendees; use HiEvents\DomainObjects\EventDomainObject; -use HiEvents\Exceptions\NoTicketsAvailableException; +use HiEvents\Exceptions\NoProductsAvailableException; use HiEvents\Http\Actions\BaseAction; use HiEvents\Http\Request\Attendee\EditAttendeeRequest; use HiEvents\Resources\Attendee\AttendeeResource; @@ -33,14 +33,14 @@ public function __invoke(EditAttendeeRequest $request, int $eventId, int $attend 'first_name' => $request->input('first_name'), 'last_name' => $request->input('last_name'), 'email' => $request->input('email'), - 'ticket_id' => $request->input('ticket_id'), - 'ticket_price_id' => $request->input('ticket_price_id'), + 'product_id' => $request->input('product_id'), + 'product_price_id' => $request->input('product_price_id'), 'event_id' => $eventId, 'attendee_id' => $attendeeId, ])); - } catch (NoTicketsAvailableException $exception) { + } catch (NoProductsAvailableException $exception) { throw ValidationException::withMessages([ - 'ticket_id' => $exception->getMessage(), + 'product_id' => $exception->getMessage(), ]); } diff --git a/backend/app/Http/Actions/Attendees/ExportAttendeesAction.php b/backend/app/Http/Actions/Attendees/ExportAttendeesAction.php index 3d36e38c42..990c263aaa 100644 --- a/backend/app/Http/Actions/Attendees/ExportAttendeesAction.php +++ b/backend/app/Http/Actions/Attendees/ExportAttendeesAction.php @@ -35,7 +35,7 @@ public function __invoke(int $eventId): BinaryFileResponse $questions = $this->questionRepository->findWhere([ 'event_id' => $eventId, - 'belongs_to' => QuestionBelongsTo::TICKET->name, + 'belongs_to' => QuestionBelongsTo::PRODUCT->name, ]); return Excel::download( diff --git a/backend/app/Http/Actions/Attendees/GetAttendeeAction.php b/backend/app/Http/Actions/Attendees/GetAttendeeAction.php index 6d82bbbd21..8bb96d2740 100644 --- a/backend/app/Http/Actions/Attendees/GetAttendeeAction.php +++ b/backend/app/Http/Actions/Attendees/GetAttendeeAction.php @@ -4,8 +4,8 @@ use HiEvents\DomainObjects\EventDomainObject; use HiEvents\DomainObjects\QuestionAndAnswerViewDomainObject; -use HiEvents\DomainObjects\TicketDomainObject; -use HiEvents\DomainObjects\TicketPriceDomainObject; +use HiEvents\DomainObjects\ProductDomainObject; +use HiEvents\DomainObjects\ProductPriceDomainObject; use HiEvents\Http\Actions\BaseAction; use HiEvents\Repository\Eloquent\Value\Relationship; use HiEvents\Repository\Interfaces\AttendeeRepositoryInterface; @@ -29,12 +29,12 @@ public function __invoke(int $eventId, int $attendeeId): Response|JsonResponse $attendee = $this->attendeeRepository ->loadRelation(QuestionAndAnswerViewDomainObject::class) ->loadRelation(new Relationship( - domainObject: TicketDomainObject::class, + domainObject: ProductDomainObject::class, nested: [ new Relationship( - domainObject: TicketPriceDomainObject::class, + domainObject: ProductPriceDomainObject::class, ), - ], name: 'ticket')) + ], name: 'product')) ->findFirstWhere([ 'id' => $attendeeId, 'event_id' => $eventId, diff --git a/backend/app/Http/Actions/Attendees/GetAttendeeActionPublic.php b/backend/app/Http/Actions/Attendees/GetAttendeeActionPublic.php index 87d0b2f88c..5be7ddf396 100644 --- a/backend/app/Http/Actions/Attendees/GetAttendeeActionPublic.php +++ b/backend/app/Http/Actions/Attendees/GetAttendeeActionPublic.php @@ -3,8 +3,8 @@ namespace HiEvents\Http\Actions\Attendees; use HiEvents\DomainObjects\Generated\AttendeeDomainObjectAbstract; -use HiEvents\DomainObjects\TicketDomainObject; -use HiEvents\DomainObjects\TicketPriceDomainObject; +use HiEvents\DomainObjects\ProductDomainObject; +use HiEvents\DomainObjects\ProductPriceDomainObject; use HiEvents\Http\Actions\BaseAction; use HiEvents\Repository\Eloquent\Value\Relationship; use HiEvents\Repository\Interfaces\AttendeeRepositoryInterface; @@ -28,12 +28,12 @@ public function __invoke(int $eventId, string $attendeeShortId): JsonResponse|Re { $attendee = $this->attendeeRepository ->loadRelation(new Relationship( - domainObject: TicketDomainObject::class, + domainObject: ProductDomainObject::class, nested: [ new Relationship( - domainObject: TicketPriceDomainObject::class, + domainObject: ProductPriceDomainObject::class, ), - ], name: 'ticket')) + ], name: 'product')) ->findFirstWhere([ AttendeeDomainObjectAbstract::SHORT_ID => $attendeeShortId ]); diff --git a/backend/app/Http/Actions/CapacityAssignments/CreateCapacityAssignmentAction.php b/backend/app/Http/Actions/CapacityAssignments/CreateCapacityAssignmentAction.php index 0eae8c0ded..8450d86aba 100644 --- a/backend/app/Http/Actions/CapacityAssignments/CreateCapacityAssignmentAction.php +++ b/backend/app/Http/Actions/CapacityAssignments/CreateCapacityAssignmentAction.php @@ -6,7 +6,7 @@ use HiEvents\Http\Actions\BaseAction; use HiEvents\Http\Request\CapacityAssigment\UpsertCapacityAssignmentRequest; use HiEvents\Resources\CapacityAssignment\CapacityAssignmentResource; -use HiEvents\Services\Domain\Ticket\Exception\UnrecognizedTicketIdException; +use HiEvents\Services\Domain\Product\Exception\UnrecognizedProductIdException; use HiEvents\Services\Handlers\CapacityAssignment\CreateCapacityAssignmentHandler; use HiEvents\Services\Handlers\CapacityAssignment\DTO\UpsertCapacityAssignmentDTO; use Illuminate\Http\JsonResponse; @@ -31,10 +31,10 @@ public function __invoke(int $eventId, UpsertCapacityAssignmentRequest $request) 'event_id' => $eventId, 'capacity' => $request->validated('capacity'), 'status' => $request->validated('status'), - 'ticket_ids' => $request->validated('ticket_ids'), + 'product_ids' => $request->validated('product_ids'), ]), ); - } catch (UnrecognizedTicketIdException $exception) { + } catch (UnrecognizedProductIdException $exception) { return $this->errorResponse( message: $exception->getMessage(), statusCode: Response::HTTP_UNPROCESSABLE_ENTITY, diff --git a/backend/app/Http/Actions/CapacityAssignments/UpdateCapacityAssignmentAction.php b/backend/app/Http/Actions/CapacityAssignments/UpdateCapacityAssignmentAction.php index 0deb55fb3b..3af2d794a1 100644 --- a/backend/app/Http/Actions/CapacityAssignments/UpdateCapacityAssignmentAction.php +++ b/backend/app/Http/Actions/CapacityAssignments/UpdateCapacityAssignmentAction.php @@ -6,7 +6,7 @@ use HiEvents\Http\Actions\BaseAction; use HiEvents\Http\Request\CapacityAssigment\UpsertCapacityAssignmentRequest; use HiEvents\Resources\CapacityAssignment\CapacityAssignmentResource; -use HiEvents\Services\Domain\Ticket\Exception\UnrecognizedTicketIdException; +use HiEvents\Services\Domain\Product\Exception\UnrecognizedProductIdException; use HiEvents\Services\Handlers\CapacityAssignment\DTO\UpsertCapacityAssignmentDTO; use HiEvents\Services\Handlers\CapacityAssignment\UpdateCapacityAssignmentHandler; use Illuminate\Http\JsonResponse; @@ -33,10 +33,10 @@ public function __invoke(int $eventId, int $capacityAssignmentId, UpsertCapacity 'capacity' => $request->validated('capacity'), 'applies_to' => $request->validated('applies_to'), 'status' => $request->validated('status'), - 'ticket_ids' => $request->validated('ticket_ids'), + 'product_ids' => $request->validated('product_ids'), ]), ); - } catch (UnrecognizedTicketIdException $exception) { + } catch (UnrecognizedProductIdException $exception) { return $this->errorResponse( message: $exception->getMessage(), statusCode: Response::HTTP_UNPROCESSABLE_ENTITY, diff --git a/backend/app/Http/Actions/CheckInLists/CreateCheckInListAction.php b/backend/app/Http/Actions/CheckInLists/CreateCheckInListAction.php index c10ada722e..80f9c55bb4 100644 --- a/backend/app/Http/Actions/CheckInLists/CreateCheckInListAction.php +++ b/backend/app/Http/Actions/CheckInLists/CreateCheckInListAction.php @@ -5,7 +5,7 @@ use HiEvents\Http\Actions\BaseAction; use HiEvents\Http\Request\CheckInList\UpsertCheckInListRequest; use HiEvents\Resources\CheckInList\CheckInListResource; -use HiEvents\Services\Domain\Ticket\Exception\UnrecognizedTicketIdException; +use HiEvents\Services\Domain\Product\Exception\UnrecognizedProductIdException; use HiEvents\Services\Handlers\CheckInList\CreateCheckInListHandler; use HiEvents\Services\Handlers\CheckInList\DTO\UpsertCheckInListDTO; use Illuminate\Http\JsonResponse; @@ -27,12 +27,12 @@ public function __invoke(UpsertCheckInListRequest $request, int $eventId): JsonR name: $request->validated('name'), description: $request->validated('description'), eventId: $eventId, - ticketIds: $request->validated('ticket_ids'), + productIds: $request->validated('product_ids'), expiresAt: $request->validated('expires_at'), activatesAt: $request->validated('activates_at'), ) ); - } catch (UnrecognizedTicketIdException $exception) { + } catch (UnrecognizedProductIdException $exception) { return $this->errorResponse( message: $exception->getMessage(), statusCode: Response::HTTP_UNPROCESSABLE_ENTITY, diff --git a/backend/app/Http/Actions/CheckInLists/UpdateCheckInListAction.php b/backend/app/Http/Actions/CheckInLists/UpdateCheckInListAction.php index 5599617dae..f401d784c4 100644 --- a/backend/app/Http/Actions/CheckInLists/UpdateCheckInListAction.php +++ b/backend/app/Http/Actions/CheckInLists/UpdateCheckInListAction.php @@ -6,7 +6,7 @@ use HiEvents\Http\Actions\BaseAction; use HiEvents\Http\Request\CheckInList\UpsertCheckInListRequest; use HiEvents\Resources\CheckInList\CheckInListResource; -use HiEvents\Services\Domain\Ticket\Exception\UnrecognizedTicketIdException; +use HiEvents\Services\Domain\Product\Exception\UnrecognizedProductIdException; use HiEvents\Services\Handlers\CheckInList\DTO\UpsertCheckInListDTO; use HiEvents\Services\Handlers\CheckInList\UpdateCheckInlistHandler; use Illuminate\Http\JsonResponse; @@ -30,13 +30,13 @@ public function __invoke(UpsertCheckInListRequest $request, int $eventId, int $c name: $request->validated('name'), description: $request->validated('description'), eventId: $eventId, - ticketIds: $request->validated('ticket_ids'), + productIds: $request->validated('product_ids'), expiresAt: $request->validated('expires_at'), activatesAt: $request->validated('activates_at'), id: $checkInListId, ) ); - } catch (UnrecognizedTicketIdException $exception) { + } catch (UnrecognizedProductIdException $exception) { return $this->errorResponse( message: $exception->getMessage(), statusCode: Response::HTTP_UNPROCESSABLE_ENTITY, diff --git a/backend/app/Http/Actions/Events/DuplicateEventAction.php b/backend/app/Http/Actions/Events/DuplicateEventAction.php index 2f6063da0c..2d69e876d8 100644 --- a/backend/app/Http/Actions/Events/DuplicateEventAction.php +++ b/backend/app/Http/Actions/Events/DuplicateEventAction.php @@ -29,7 +29,7 @@ public function __invoke(int $eventId, DuplicateEventRequest $request): JsonResp accountId: $this->getAuthenticatedAccountId(), title: $request->validated('title'), startDate: $request->validated('start_date'), - duplicateTickets: $request->validated('duplicate_tickets'), + duplicateProducts: $request->validated('duplicate_products'), duplicateQuestions: $request->validated('duplicate_questions'), duplicateSettings: $request->validated('duplicate_settings'), duplicatePromoCodes: $request->validated('duplicate_promo_codes'), diff --git a/backend/app/Http/Actions/Events/GetEventAction.php b/backend/app/Http/Actions/Events/GetEventAction.php index d2ac6f9c50..f1ca761169 100644 --- a/backend/app/Http/Actions/Events/GetEventAction.php +++ b/backend/app/Http/Actions/Events/GetEventAction.php @@ -6,9 +6,10 @@ use HiEvents\DomainObjects\EventDomainObject; use HiEvents\DomainObjects\OrganizerDomainObject; +use HiEvents\DomainObjects\ProductCategoryDomainObject; use HiEvents\DomainObjects\TaxAndFeesDomainObject; -use HiEvents\DomainObjects\TicketDomainObject; -use HiEvents\DomainObjects\TicketPriceDomainObject; +use HiEvents\DomainObjects\ProductDomainObject; +use HiEvents\DomainObjects\ProductPriceDomainObject; use HiEvents\Http\Actions\BaseAction; use HiEvents\Repository\Eloquent\Value\Relationship; use HiEvents\Repository\Interfaces\EventRepositoryInterface; @@ -31,10 +32,12 @@ public function __invoke(int $eventId): JsonResponse $event = $this->eventRepository ->loadRelation(new Relationship(domainObject: OrganizerDomainObject::class, name: 'organizer')) ->loadRelation( - new Relationship(TicketDomainObject::class, [ - new Relationship(TicketPriceDomainObject::class), - new Relationship(TaxAndFeesDomainObject::class), - ]), + new Relationship(ProductCategoryDomainObject::class, [ + new Relationship(ProductDomainObject::class, [ + new Relationship(ProductPriceDomainObject::class), + new Relationship(TaxAndFeesDomainObject::class), + ]), + ]) ) ->findById($eventId); diff --git a/backend/app/Http/Actions/Events/GetEventPublicAction.php b/backend/app/Http/Actions/Events/GetEventPublicAction.php index 144720264f..4fc6c1735a 100644 --- a/backend/app/Http/Actions/Events/GetEventPublicAction.php +++ b/backend/app/Http/Actions/Events/GetEventPublicAction.php @@ -15,7 +15,7 @@ class GetEventPublicAction extends BaseAction { public function __construct( - private readonly GetPublicEventHandler $handler, + private readonly GetPublicEventHandler $getPublicEventHandler, private readonly LoggerInterface $logger, ) { @@ -23,7 +23,7 @@ public function __construct( public function __invoke(int $eventId, Request $request): Response|JsonResponse { - $event = $this->handler->handle(GetPublicEventDTO::fromArray([ + $event = $this->getPublicEventHandler->handle(GetPublicEventDTO::fromArray([ 'eventId' => $eventId, 'ipAddress' => $this->getClientIp($request), 'promoCode' => strtolower($request->string('promo_code')), diff --git a/backend/app/Http/Actions/Messages/SendMessageAction.php b/backend/app/Http/Actions/Messages/SendMessageAction.php index 5bdb1910ec..bf0220da03 100644 --- a/backend/app/Http/Actions/Messages/SendMessageAction.php +++ b/backend/app/Http/Actions/Messages/SendMessageAction.php @@ -36,7 +36,7 @@ public function __invoke(SendMessageRequest $request, int $eventId): JsonRespons 'is_test' => $request->input('is_test'), 'order_id' => $request->input('order_id'), 'attendee_ids' => $request->input('attendee_ids'), - 'ticket_ids' => $request->input('ticket_ids'), + 'product_ids' => $request->input('product_ids'), 'send_copy_to_current_user' => $request->boolean('send_copy_to_current_user'), 'sent_by_user_id' => $user->getId(), 'account_id' => $this->getAuthenticatedAccountId(), diff --git a/backend/app/Http/Actions/Orders/CompleteOrderActionPublic.php b/backend/app/Http/Actions/Orders/CompleteOrderActionPublic.php index 619c1dcbd1..acfd466e48 100644 --- a/backend/app/Http/Actions/Orders/CompleteOrderActionPublic.php +++ b/backend/app/Http/Actions/Orders/CompleteOrderActionPublic.php @@ -33,7 +33,7 @@ public function __invoke(CompleteOrderRequest $request, int $eventId, string $or ? $request->input('order.questions') : null, ]), - 'attendees' => $request->input('attendees'), + 'products' => $request->input('products'), ])); } catch (ResourceConflictException $e) { return $this->errorResponse($e->getMessage(), Response::HTTP_CONFLICT); diff --git a/backend/app/Http/Actions/Orders/CreateOrderActionPublic.php b/backend/app/Http/Actions/Orders/CreateOrderActionPublic.php index c789ae6cf1..9c7db9d16b 100644 --- a/backend/app/Http/Actions/Orders/CreateOrderActionPublic.php +++ b/backend/app/Http/Actions/Orders/CreateOrderActionPublic.php @@ -12,7 +12,7 @@ use HiEvents\Services\Domain\Order\OrderCreateRequestValidationService; use HiEvents\Services\Handlers\Order\CreateOrderHandler; use HiEvents\Services\Handlers\Order\DTO\CreateOrderPublicDTO; -use HiEvents\Services\Handlers\Order\DTO\TicketOrderDetailsDTO; +use HiEvents\Services\Handlers\Order\DTO\ProductOrderDetailsDTO; use HiEvents\Services\Infrastructure\Session\CheckoutSessionManagementService; use Illuminate\Http\JsonResponse; use Throwable; @@ -42,7 +42,7 @@ public function __invoke(CreateOrderRequest $request, int $eventId): JsonRespons CreateOrderPublicDTO::fromArray([ 'is_user_authenticated' => $this->isUserAuthenticated(), 'promo_code' => $request->input('promo_code'), - 'tickets' => TicketOrderDetailsDTO::collectionFromArray($request->input('tickets')), + 'products' => ProductOrderDetailsDTO::collectionFromArray($request->input('products')), 'session_identifier' => $sessionId, 'order_locale' => $this->localeService->getLocaleOrDefault($request->getPreferredLanguage()), ]) diff --git a/backend/app/Http/Actions/ProductCategories/CreateProductCategoryAction.php b/backend/app/Http/Actions/ProductCategories/CreateProductCategoryAction.php new file mode 100644 index 0000000000..1a62b422e4 --- /dev/null +++ b/backend/app/Http/Actions/ProductCategories/CreateProductCategoryAction.php @@ -0,0 +1,40 @@ +isActionAuthorized($eventId, EventDomainObject::class); + + $productCategory = $this->handler->handle(new UpsertProductCategoryDTO( + name: $request->validated('name'), + description: $request->validated('description'), + is_hidden: $request->validated('is_hidden'), + event_id: $eventId, + no_products_message: $request->validated('no_products_message'), + )); + + return $this->resourceResponse( + resource: ProductCategoryResource::class, + data: $productCategory, + statusCode: ResponseCodes::HTTP_CREATED, + ); + } +} diff --git a/backend/app/Http/Actions/ProductCategories/DeleteProductCategoryAction.php b/backend/app/Http/Actions/ProductCategories/DeleteProductCategoryAction.php new file mode 100644 index 0000000000..80ec3e4623 --- /dev/null +++ b/backend/app/Http/Actions/ProductCategories/DeleteProductCategoryAction.php @@ -0,0 +1,46 @@ +isActionAuthorized($eventId, EventDomainObject::class); + + try { + $this->deleteProductCategoryHandler->handle( + productCategoryId: $productCategoryId, + eventId: $eventId, + ); + } catch (CannotDeleteEntityException $exception) { + return $this->errorResponse( + message: $exception->getMessage(), + statusCode: Response::HTTP_CONFLICT, + ); + } + + return $this->deletedResponse(); + } +} diff --git a/backend/app/Http/Actions/ProductCategories/EditProductCategoryAction.php b/backend/app/Http/Actions/ProductCategories/EditProductCategoryAction.php new file mode 100644 index 0000000000..f6f91fda21 --- /dev/null +++ b/backend/app/Http/Actions/ProductCategories/EditProductCategoryAction.php @@ -0,0 +1,42 @@ +isActionAuthorized($eventId, EventDomainObject::class); + + $request->merge([ + 'event_id' => $eventId, + 'account_id' => $this->getAuthenticatedAccountId(), + 'product_category_id' => $productCategoryId, + ]); + + $productCategory = $this->editProductCategoryHandler->handle(new UpsertProductCategoryDTO( + name: $request->validated('name'), + description: $request->validated('description'), + is_hidden: $request->validated('is_hidden'), + event_id: $eventId, + no_products_message: $request->validated('no_products_message'), + product_category_id: $productCategoryId, + )); + + return $this->resourceResponse(ProductCategoryResource::class, $productCategory); + } +} diff --git a/backend/app/Http/Actions/ProductCategories/GetProductCategoriesAction.php b/backend/app/Http/Actions/ProductCategories/GetProductCategoriesAction.php new file mode 100644 index 0000000000..ac37d246b3 --- /dev/null +++ b/backend/app/Http/Actions/ProductCategories/GetProductCategoriesAction.php @@ -0,0 +1,30 @@ +isActionAuthorized($eventId, EventDomainObject::class); + + $categories = $this->getProductCategoriesHandler->handle($eventId); + + return $this->resourceResponse( + resource: ProductCategoryResource::class, + data: $categories, + ); + } +} diff --git a/backend/app/Http/Actions/ProductCategories/GetProductCategoryAction.php b/backend/app/Http/Actions/ProductCategories/GetProductCategoryAction.php new file mode 100644 index 0000000000..d4ca961b3e --- /dev/null +++ b/backend/app/Http/Actions/ProductCategories/GetProductCategoryAction.php @@ -0,0 +1,30 @@ +isActionAuthorized($eventId, EventDomainObject::class); + + $category = $this->getProductCategoryHandler->handle($eventId, $productCategoryId); + + return $this->resourceResponse( + resource: ProductCategoryResource::class, + data: $category, + ); + } +} diff --git a/backend/app/Http/Actions/Tickets/CreateTicketAction.php b/backend/app/Http/Actions/Products/CreateProductAction.php similarity index 54% rename from backend/app/Http/Actions/Tickets/CreateTicketAction.php rename to backend/app/Http/Actions/Products/CreateProductAction.php index 7340b99b6a..ac46cf7fde 100644 --- a/backend/app/Http/Actions/Tickets/CreateTicketAction.php +++ b/backend/app/Http/Actions/Products/CreateProductAction.php @@ -2,33 +2,33 @@ declare(strict_types=1); -namespace HiEvents\Http\Actions\Tickets; +namespace HiEvents\Http\Actions\Products; use HiEvents\DomainObjects\EventDomainObject; use HiEvents\Exceptions\InvalidTaxOrFeeIdException; use HiEvents\Http\Actions\BaseAction; -use HiEvents\Http\Request\Ticket\UpsertTicketRequest; +use HiEvents\Http\Request\Product\UpsertProductRequest; use HiEvents\Http\ResponseCodes; -use HiEvents\Resources\Ticket\TicketResource; -use HiEvents\Services\Handlers\Ticket\CreateTicketHandler; -use HiEvents\Services\Handlers\Ticket\DTO\UpsertTicketDTO; +use HiEvents\Resources\Product\ProductResource; +use HiEvents\Services\Handlers\Product\CreateProductHandler; +use HiEvents\Services\Handlers\Product\DTO\UpsertProductDTO; use Illuminate\Http\JsonResponse; use Illuminate\Validation\ValidationException; use Throwable; -class CreateTicketAction extends BaseAction +class CreateProductAction extends BaseAction { - private CreateTicketHandler $createTicketHandler; + private CreateProductHandler $createProductHandler; - public function __construct(CreateTicketHandler $handler) + public function __construct(CreateProductHandler $handler) { - $this->createTicketHandler = $handler; + $this->createProductHandler = $handler; } /** * @throws Throwable */ - public function __invoke(int $eventId, UpsertTicketRequest $request): JsonResponse + public function __invoke(int $eventId, UpsertProductRequest $request): JsonResponse { $this->isActionAuthorized($eventId, EventDomainObject::class); @@ -38,7 +38,7 @@ public function __invoke(int $eventId, UpsertTicketRequest $request): JsonRespon ]); try { - $ticket = $this->createTicketHandler->handle(UpsertTicketDTO::fromArray($request->all())); + $product = $this->createProductHandler->handle(UpsertProductDTO::fromArray($request->all())); } catch (InvalidTaxOrFeeIdException $e) { throw ValidationException::withMessages([ 'tax_and_fee_ids' => $e->getMessage(), @@ -46,8 +46,8 @@ public function __invoke(int $eventId, UpsertTicketRequest $request): JsonRespon } return $this->resourceResponse( - resource: TicketResource::class, - data: $ticket, + resource: ProductResource::class, + data: $product, statusCode: ResponseCodes::HTTP_CREATED, ); } diff --git a/backend/app/Http/Actions/Tickets/DeleteTicketAction.php b/backend/app/Http/Actions/Products/DeleteProductAction.php similarity index 60% rename from backend/app/Http/Actions/Tickets/DeleteTicketAction.php rename to backend/app/Http/Actions/Products/DeleteProductAction.php index 7c51cad3f1..3979c48690 100644 --- a/backend/app/Http/Actions/Tickets/DeleteTicketAction.php +++ b/backend/app/Http/Actions/Products/DeleteProductAction.php @@ -2,32 +2,32 @@ declare(strict_types=1); -namespace HiEvents\Http\Actions\Tickets; +namespace HiEvents\Http\Actions\Products; use HiEvents\DomainObjects\EventDomainObject; use HiEvents\Exceptions\CannotDeleteEntityException; use HiEvents\Http\Actions\BaseAction; -use HiEvents\Services\Handlers\Ticket\DeleteTicketHandler; +use HiEvents\Services\Handlers\Product\DeleteProductHandler; use Illuminate\Http\JsonResponse; use Illuminate\Http\Response; use Symfony\Component\HttpFoundation\Response as HttpResponse; -class DeleteTicketAction extends BaseAction +class DeleteProductAction extends BaseAction { - private DeleteTicketHandler $deleteTicketHandler; + private DeleteProductHandler $deleteProductHandler; - public function __construct(DeleteTicketHandler $handler) + public function __construct(DeleteProductHandler $handler) { - $this->deleteTicketHandler = $handler; + $this->deleteProductHandler = $handler; } - public function __invoke(int $eventId, int $ticketId): Response|JsonResponse + public function __invoke(int $eventId, int $productId): Response|JsonResponse { $this->isActionAuthorized($eventId, EventDomainObject::class); try { - $this->deleteTicketHandler->handle( - ticketId: $ticketId, + $this->deleteProductHandler->handle( + productId: $productId, eventId: $eventId, ); } catch (CannotDeleteEntityException $exception) { diff --git a/backend/app/Http/Actions/Tickets/EditTicketAction.php b/backend/app/Http/Actions/Products/EditProductAction.php similarity index 53% rename from backend/app/Http/Actions/Tickets/EditTicketAction.php rename to backend/app/Http/Actions/Products/EditProductAction.php index d03111e83a..79d6fe94bc 100644 --- a/backend/app/Http/Actions/Tickets/EditTicketAction.php +++ b/backend/app/Http/Actions/Products/EditProductAction.php @@ -2,24 +2,24 @@ declare(strict_types=1); -namespace HiEvents\Http\Actions\Tickets; +namespace HiEvents\Http\Actions\Products; use HiEvents\DomainObjects\EventDomainObject; -use HiEvents\Exceptions\CannotChangeTicketTypeException; +use HiEvents\Exceptions\CannotChangeProductTypeException; use HiEvents\Exceptions\InvalidTaxOrFeeIdException; use HiEvents\Http\Actions\BaseAction; -use HiEvents\Http\Request\Ticket\UpsertTicketRequest; -use HiEvents\Resources\Ticket\TicketResource; -use HiEvents\Services\Handlers\Ticket\DTO\UpsertTicketDTO; -use HiEvents\Services\Handlers\Ticket\EditTicketHandler; +use HiEvents\Http\Request\Product\UpsertProductRequest; +use HiEvents\Resources\Product\ProductResource; +use HiEvents\Services\Handlers\Product\DTO\UpsertProductDTO; +use HiEvents\Services\Handlers\Product\EditProductHandler; use Illuminate\Http\JsonResponse; use Illuminate\Validation\ValidationException; use Throwable; -class EditTicketAction extends BaseAction +class EditProductAction extends BaseAction { public function __construct( - private readonly EditTicketHandler $editTicketHandler, + private readonly EditProductHandler $editProductHandler, ) { } @@ -28,28 +28,28 @@ public function __construct( * @throws Throwable * @throws ValidationException */ - public function __invoke(UpsertTicketRequest $request, int $eventId, int $ticketId): JsonResponse + public function __invoke(UpsertProductRequest $request, int $eventId, int $productId): JsonResponse { $this->isActionAuthorized($eventId, EventDomainObject::class); $request->merge([ 'event_id' => $eventId, 'account_id' => $this->getAuthenticatedAccountId(), - 'ticket_id' => $ticketId, + 'product_id' => $productId, ]); try { - $ticket = $this->editTicketHandler->handle(UpsertTicketDTO::fromArray($request->all())); + $product = $this->editProductHandler->handle(UpsertProductDTO::fromArray($request->all())); } catch (InvalidTaxOrFeeIdException $e) { throw ValidationException::withMessages([ 'tax_and_fee_ids' => $e->getMessage(), ]); - } catch (CannotChangeTicketTypeException $e) { + } catch (CannotChangeProductTypeException $e) { throw ValidationException::withMessages([ 'type' => $e->getMessage(), ]); } - return $this->resourceResponse(TicketResource::class, $ticket); + return $this->resourceResponse(ProductResource::class, $product); } } diff --git a/backend/app/Http/Actions/Products/GetProductAction.php b/backend/app/Http/Actions/Products/GetProductAction.php new file mode 100644 index 0000000000..f73ec84749 --- /dev/null +++ b/backend/app/Http/Actions/Products/GetProductAction.php @@ -0,0 +1,37 @@ +productRepository = $productRepository; + } + + public function __invoke(int $eventId, int $productId): JsonResponse + { + $this->isActionAuthorized($eventId, EventDomainObject::class); + + return $this->resourceResponse(ProductResource::class, $this->productRepository + ->loadRelation(TaxAndFeesDomainObject::class) + ->loadRelation(ProductPriceDomainObject::class) + ->findFirstWhere([ + ProductDomainObjectAbstract::EVENT_ID => $eventId, + ProductDomainObjectAbstract::ID => $productId, + ])); + } +} diff --git a/backend/app/Http/Actions/Tickets/GetTicketsAction.php b/backend/app/Http/Actions/Products/GetProductsAction.php similarity index 53% rename from backend/app/Http/Actions/Tickets/GetTicketsAction.php rename to backend/app/Http/Actions/Products/GetProductsAction.php index e962de2692..b7f03a2dea 100644 --- a/backend/app/Http/Actions/Tickets/GetTicketsAction.php +++ b/backend/app/Http/Actions/Products/GetProductsAction.php @@ -2,20 +2,20 @@ declare(strict_types=1); -namespace HiEvents\Http\Actions\Tickets; +namespace HiEvents\Http\Actions\Products; use HiEvents\DomainObjects\EventDomainObject; -use HiEvents\DomainObjects\TicketDomainObject; +use HiEvents\DomainObjects\ProductDomainObject; use HiEvents\Http\Actions\BaseAction; -use HiEvents\Resources\Ticket\TicketResource; -use HiEvents\Services\Handlers\Ticket\GetTicketsHandler; +use HiEvents\Resources\Product\ProductResource; +use HiEvents\Services\Handlers\Product\GetProductsHandler; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; -class GetTicketsAction extends BaseAction +class GetProductsAction extends BaseAction { public function __construct( - private readonly GetTicketsHandler $getTicketsHandler, + private readonly GetProductsHandler $getProductsHandler, ) { } @@ -24,15 +24,15 @@ public function __invoke(int $eventId, Request $request): JsonResponse { $this->isActionAuthorized($eventId, EventDomainObject::class); - $tickets = $this->getTicketsHandler->handle( + $products = $this->getProductsHandler->handle( eventId: $eventId, queryParamsDTO: $this->getPaginationQueryParams($request), ); return $this->filterableResourceResponse( - resource: TicketResource::class, - data: $tickets, - domainObject: TicketDomainObject::class + resource: ProductResource::class, + data: $products, + domainObject: ProductDomainObject::class ); } } diff --git a/backend/app/Http/Actions/Tickets/SortTicketsAction.php b/backend/app/Http/Actions/Products/SortProductsAction.php similarity index 55% rename from backend/app/Http/Actions/Tickets/SortTicketsAction.php rename to backend/app/Http/Actions/Products/SortProductsAction.php index 1e17775722..43a24f29b5 100644 --- a/backend/app/Http/Actions/Tickets/SortTicketsAction.php +++ b/backend/app/Http/Actions/Products/SortProductsAction.php @@ -1,31 +1,31 @@ isActionAuthorized($eventId, EventDomainObject::class); try { - $this->sortTicketsHandler->handle( + $this->sortProductsHandler->handle( $eventId, - $request->validated(), + $request->validated('sorted_categories'), ); } catch (ResourceConflictException $e) { return $this->errorResponse($e->getMessage(), Response::HTTP_CONFLICT); diff --git a/backend/app/Http/Actions/PromoCodes/CreatePromoCodeAction.php b/backend/app/Http/Actions/PromoCodes/CreatePromoCodeAction.php index f1c8b8b74e..4631e88cf8 100644 --- a/backend/app/Http/Actions/PromoCodes/CreatePromoCodeAction.php +++ b/backend/app/Http/Actions/PromoCodes/CreatePromoCodeAction.php @@ -9,7 +9,7 @@ use HiEvents\Http\Request\PromoCode\CreateUpdatePromoCodeRequest; use HiEvents\Http\ResponseCodes; use HiEvents\Resources\PromoCode\PromoCodeResource; -use HiEvents\Services\Domain\Ticket\Exception\UnrecognizedTicketIdException; +use HiEvents\Services\Domain\Product\Exception\UnrecognizedProductIdException; use HiEvents\Services\Handlers\PromoCode\CreatePromoCodeHandler; use HiEvents\Services\Handlers\PromoCode\DTO\UpsertPromoCodeDTO; use Illuminate\Http\JsonResponse; @@ -35,7 +35,7 @@ public function __invoke(CreateUpdatePromoCodeRequest $request, int $eventId): J $promoCode = $this->createPromoCodeHandler->handle($eventId, new UpsertPromoCodeDTO( code: strtolower($request->input('code')), event_id: $eventId, - applicable_ticket_ids: $request->input('applicable_ticket_ids'), + applicable_product_ids: $request->input('applicable_product_ids'), discount_type: PromoCodeDiscountTypeEnum::fromName($request->input('discount_type')), discount: $request->float('discount'), expiry_date: $request->input('expiry_date'), @@ -45,9 +45,9 @@ public function __invoke(CreateUpdatePromoCodeRequest $request, int $eventId): J throw ValidationException::withMessages([ 'code' => $e->getMessage(), ]); - } catch (UnrecognizedTicketIdException $e) { + } catch (UnrecognizedProductIdException $e) { throw ValidationException::withMessages([ - 'applicable_ticket_ids' => $e->getMessage(), + 'applicable_product_ids' => $e->getMessage(), ]); } diff --git a/backend/app/Http/Actions/PromoCodes/UpdatePromoCodeAction.php b/backend/app/Http/Actions/PromoCodes/UpdatePromoCodeAction.php index 0416ed8073..b3c4c5c467 100644 --- a/backend/app/Http/Actions/PromoCodes/UpdatePromoCodeAction.php +++ b/backend/app/Http/Actions/PromoCodes/UpdatePromoCodeAction.php @@ -9,7 +9,7 @@ use HiEvents\Http\Request\PromoCode\CreateUpdatePromoCodeRequest; use HiEvents\Http\ResponseCodes; use HiEvents\Resources\PromoCode\PromoCodeResource; -use HiEvents\Services\Domain\Ticket\Exception\UnrecognizedTicketIdException; +use HiEvents\Services\Domain\Product\Exception\UnrecognizedProductIdException; use HiEvents\Services\Handlers\PromoCode\DTO\UpsertPromoCodeDTO; use HiEvents\Services\Handlers\PromoCode\UpdatePromoCodeHandler; use Illuminate\Http\JsonResponse; @@ -35,7 +35,7 @@ public function __invoke(CreateUpdatePromoCodeRequest $request, int $eventId, in $promoCode = $this->updatePromoCodeHandler->handle($promoCodeId, new UpsertPromoCodeDTO( code: strtolower($request->input('code')), event_id: $eventId, - applicable_ticket_ids: $request->input('applicable_ticket_ids'), + applicable_product_ids: $request->input('applicable_product_ids'), discount_type: PromoCodeDiscountTypeEnum::fromName($request->input('discount_type')), discount: $request->float('discount'), expiry_date: $request->input('expiry_date'), @@ -45,9 +45,9 @@ public function __invoke(CreateUpdatePromoCodeRequest $request, int $eventId, in throw ValidationException::withMessages([ 'code' => $e->getMessage(), ]); - } catch (UnrecognizedTicketIdException $e) { + } catch (UnrecognizedProductIdException $e) { throw ValidationException::withMessages([ - 'applicable_ticket_ids' => $e->getMessage(), + 'applicable_product_ids' => $e->getMessage(), ]); } diff --git a/backend/app/Http/Actions/Questions/CreateQuestionAction.php b/backend/app/Http/Actions/Questions/CreateQuestionAction.php index a5a9ca71dd..d13b8b0d89 100644 --- a/backend/app/Http/Actions/Questions/CreateQuestionAction.php +++ b/backend/app/Http/Actions/Questions/CreateQuestionAction.php @@ -30,7 +30,7 @@ public function __invoke(UpsertQuestionRequest $request, int $eventId): JsonResp 'required' => $request->boolean('required'), 'options' => $request->input('options'), 'event_id' => $eventId, - 'ticket_ids' => $request->input('ticket_ids'), + 'product_ids' => $request->input('product_ids'), 'belongs_to' => $request->input('belongs_to'), 'is_hidden' => $request->boolean('is_hidden'), 'description' => $request->input('description'), diff --git a/backend/app/Http/Actions/Questions/EditQuestionAction.php b/backend/app/Http/Actions/Questions/EditQuestionAction.php index 616584e972..189bdec41a 100644 --- a/backend/app/Http/Actions/Questions/EditQuestionAction.php +++ b/backend/app/Http/Actions/Questions/EditQuestionAction.php @@ -37,7 +37,7 @@ public function __invoke(UpsertQuestionRequest $request, int $eventId, int $ques 'required' => $request->boolean('required'), 'options' => $request->input('options'), 'event_id' => $eventId, - 'ticket_ids' => $request->input('ticket_ids'), + 'product_ids' => $request->input('product_ids'), 'is_hidden' => $request->boolean('is_hidden'), 'belongs_to' => QuestionBelongsTo::fromName($request->input('belongs_to')), 'description' => $request->input('description'), diff --git a/backend/app/Http/Actions/Questions/GetQuestionAction.php b/backend/app/Http/Actions/Questions/GetQuestionAction.php index 6759fd3d4c..6701e7ce3f 100644 --- a/backend/app/Http/Actions/Questions/GetQuestionAction.php +++ b/backend/app/Http/Actions/Questions/GetQuestionAction.php @@ -3,7 +3,7 @@ namespace HiEvents\Http\Actions\Questions; use HiEvents\DomainObjects\EventDomainObject; -use HiEvents\DomainObjects\TicketDomainObject; +use HiEvents\DomainObjects\ProductDomainObject; use HiEvents\Http\Actions\BaseAction; use HiEvents\Repository\Interfaces\QuestionRepositoryInterface; use HiEvents\Resources\Question\QuestionResource; @@ -24,7 +24,7 @@ public function __invoke(Request $request, int $eventId, int $questionId): JsonR $this->isActionAuthorized($eventId, EventDomainObject::class); $questions = $this->questionRepository - ->loadRelation(TicketDomainObject::class) + ->loadRelation(ProductDomainObject::class) ->findById($questionId); return $this->resourceResponse(QuestionResource::class, $questions); diff --git a/backend/app/Http/Actions/Questions/GetQuestionsAction.php b/backend/app/Http/Actions/Questions/GetQuestionsAction.php index fa9fe3a4ae..4a234442c0 100644 --- a/backend/app/Http/Actions/Questions/GetQuestionsAction.php +++ b/backend/app/Http/Actions/Questions/GetQuestionsAction.php @@ -3,8 +3,8 @@ namespace HiEvents\Http\Actions\Questions; use HiEvents\DomainObjects\EventDomainObject; -use HiEvents\DomainObjects\TicketDomainObject; -use HiEvents\DomainObjects\TicketPriceDomainObject; +use HiEvents\DomainObjects\ProductDomainObject; +use HiEvents\DomainObjects\ProductPriceDomainObject; use HiEvents\Http\Actions\BaseAction; use HiEvents\Repository\Eloquent\Value\Relationship; use HiEvents\Repository\Interfaces\QuestionRepositoryInterface; @@ -27,8 +27,8 @@ public function __invoke(Request $request, int $eventId): JsonResponse $questions = $this->questionRepository ->loadRelation( - new Relationship(TicketDomainObject::class, [ - new Relationship(TicketPriceDomainObject::class) + new Relationship(ProductDomainObject::class, [ + new Relationship(ProductPriceDomainObject::class) ]) ) ->findByEventId($eventId); diff --git a/backend/app/Http/Actions/Questions/GetQuestionsPublicAction.php b/backend/app/Http/Actions/Questions/GetQuestionsPublicAction.php index 8fe8250251..8e718f2846 100644 --- a/backend/app/Http/Actions/Questions/GetQuestionsPublicAction.php +++ b/backend/app/Http/Actions/Questions/GetQuestionsPublicAction.php @@ -3,7 +3,7 @@ namespace HiEvents\Http\Actions\Questions; use HiEvents\DomainObjects\Generated\QuestionDomainObjectAbstract; -use HiEvents\DomainObjects\TicketDomainObject; +use HiEvents\DomainObjects\ProductDomainObject; use HiEvents\Http\Actions\BaseAction; use HiEvents\Repository\Interfaces\QuestionRepositoryInterface; use HiEvents\Resources\Question\QuestionResourcePublic; @@ -22,7 +22,7 @@ public function __construct(QuestionRepositoryInterface $questionRepository) public function __invoke(Request $request, int $eventId): JsonResponse { $questions = $this->questionRepository - ->loadRelation(TicketDomainObject::class) + ->loadRelation(ProductDomainObject::class) ->findWhere([ QuestionDomainObjectAbstract::EVENT_ID => $eventId, QuestionDomainObjectAbstract::IS_HIDDEN => false, diff --git a/backend/app/Http/Actions/Tickets/GetTicketAction.php b/backend/app/Http/Actions/Tickets/GetTicketAction.php deleted file mode 100644 index be28a9f4bc..0000000000 --- a/backend/app/Http/Actions/Tickets/GetTicketAction.php +++ /dev/null @@ -1,37 +0,0 @@ -ticketRepository = $ticketRepository; - } - - public function __invoke(int $eventId, int $ticketId): JsonResponse - { - $this->isActionAuthorized($eventId, EventDomainObject::class); - - return $this->resourceResponse(TicketResource::class, $this->ticketRepository - ->loadRelation(TaxAndFeesDomainObject::class) - ->loadRelation(TicketPriceDomainObject::class) - ->findFirstWhere([ - TicketDomainObjectAbstract::EVENT_ID => $eventId, - TicketDomainObjectAbstract::ID => $ticketId, - ])); - } -} diff --git a/backend/app/Http/Request/Attendee/CreateAttendeeRequest.php b/backend/app/Http/Request/Attendee/CreateAttendeeRequest.php index d5a6faee6e..0bbd3eed75 100644 --- a/backend/app/Http/Request/Attendee/CreateAttendeeRequest.php +++ b/backend/app/Http/Request/Attendee/CreateAttendeeRequest.php @@ -12,8 +12,8 @@ class CreateAttendeeRequest extends BaseRequest public function rules(): array { return [ - 'ticket_id' => ['int', 'required'], - 'ticket_price_id' => ['int', 'nullable'], + 'product_id' => ['int', 'required'], + 'product_price_id' => ['int', 'nullable'], 'email' => ['required', 'email'], 'first_name' => 'string|required', 'last_name' => 'string', diff --git a/backend/app/Http/Request/Attendee/EditAttendeeRequest.php b/backend/app/Http/Request/Attendee/EditAttendeeRequest.php index 10cf0ae4ac..0badf3043d 100644 --- a/backend/app/Http/Request/Attendee/EditAttendeeRequest.php +++ b/backend/app/Http/Request/Attendee/EditAttendeeRequest.php @@ -13,8 +13,8 @@ public function rules(): array 'email' => RulesHelper::REQUIRED_EMAIL, 'first_name' => RulesHelper::REQUIRED_STRING, 'last_name' => RulesHelper::REQUIRED_STRING, - 'ticket_id' => RulesHelper::REQUIRED_NUMERIC, - 'ticket_price_id' => RulesHelper::REQUIRED_NUMERIC, + 'product_id' => RulesHelper::REQUIRED_NUMERIC, + 'product_price_id' => RulesHelper::REQUIRED_NUMERIC, ]; } @@ -25,10 +25,10 @@ public function messages(): array 'email.email' => __('Email must be a valid email address'), 'first_name.required' => __('First name is required'), 'last_name.required' => __('Last name is required'), - 'ticket_id.required' => __('Ticket is required'), - 'ticket_price_id.required' => __('Ticket price is required'), - 'ticket_id.numeric' => '', - 'ticket_price_id.numeric' => '', + 'product_id.required' => __('Product is required'), + 'product_price_id.required' => __('Product price is required'), + 'product_id.numeric' => '', + 'product_price_id.numeric' => '', ]; } } diff --git a/backend/app/Http/Request/CapacityAssigment/UpsertCapacityAssignmentRequest.php b/backend/app/Http/Request/CapacityAssigment/UpsertCapacityAssignmentRequest.php index 0873bfac27..008000d444 100644 --- a/backend/app/Http/Request/CapacityAssigment/UpsertCapacityAssignmentRequest.php +++ b/backend/app/Http/Request/CapacityAssigment/UpsertCapacityAssignmentRequest.php @@ -15,14 +15,14 @@ public function rules(): array 'name' => RulesHelper::REQUIRED_STRING, 'capacity' => ['nullable', 'numeric', 'min:1'], 'status' => ['required', Rule::in(CapacityAssignmentStatus::valuesArray())], - 'ticket_ids' => ['required', 'array'], + 'product_ids' => ['required', 'array'], ]; } public function messages(): array { return [ - 'ticket_ids.required' => __('Please select at least one ticket.'), + 'product_ids.required' => __('Please select at least one product.'), ]; } } diff --git a/backend/app/Http/Request/CheckInList/UpsertCheckInListRequest.php b/backend/app/Http/Request/CheckInList/UpsertCheckInListRequest.php index 8405521d2a..06372e6760 100644 --- a/backend/app/Http/Request/CheckInList/UpsertCheckInListRequest.php +++ b/backend/app/Http/Request/CheckInList/UpsertCheckInListRequest.php @@ -14,7 +14,7 @@ public function rules(): array 'description' => ['nullable', 'string', 'max:255'], 'expires_at' => ['nullable', 'date'], 'activates_at' => ['nullable', 'date'], - 'ticket_ids' => ['required', 'array', 'min:1'], + 'product_ids' => ['required', 'array', 'min:1'], ]; } @@ -32,7 +32,7 @@ public function withValidator($validator): void public function messages(): array { return [ - 'ticket_ids.required' => __('Please select at least one ticket.'), + 'product_ids.required' => __('Please select at least one product.'), 'expires_at.after' => __('The expiration date must be after the activation date.'), 'activates_at.before' => __('The activation date must be before the expiration date.'), ]; diff --git a/backend/app/Http/Request/Event/DuplicateEventRequest.php b/backend/app/Http/Request/Event/DuplicateEventRequest.php index 70e0420abd..c172c118e1 100644 --- a/backend/app/Http/Request/Event/DuplicateEventRequest.php +++ b/backend/app/Http/Request/Event/DuplicateEventRequest.php @@ -14,7 +14,7 @@ public function rules(): array $eventValidations = $this->minimalRules(); $duplicateValidations = [ - 'duplicate_tickets' => ['boolean', 'required'], + 'duplicate_products' => ['boolean', 'required'], 'duplicate_questions' => ['boolean', 'required'], 'duplicate_settings' => ['boolean', 'required'], 'duplicate_promo_codes' => ['boolean', 'required'], diff --git a/backend/app/Http/Request/EventSettings/UpdateEventSettingsRequest.php b/backend/app/Http/Request/EventSettings/UpdateEventSettingsRequest.php index 4b1ad92264..5942b7c74c 100644 --- a/backend/app/Http/Request/EventSettings/UpdateEventSettingsRequest.php +++ b/backend/app/Http/Request/EventSettings/UpdateEventSettingsRequest.php @@ -69,8 +69,8 @@ public function messages(): array 'homepage_text_color' => $colorMessage, 'homepage_button_color' => $colorMessage, 'homepage_link_color' => $colorMessage, - 'homepage_ticket_widget_background_color' => $colorMessage, - 'homepage_ticket_widget_text_color' => $colorMessage, + 'homepage_product_widget_background_color' => $colorMessage, + 'homepage_product_widget_text_color' => $colorMessage, 'location_details.address_line_1.required_with' => __('The address line 1 field is required'), 'location_details.city.required_with' => __('The city field is required'), 'location_details.zip_or_postal_code.required_with' => __('The zip or postal code field is required'), diff --git a/backend/app/Http/Request/Message/SendMessageRequest.php b/backend/app/Http/Request/Message/SendMessageRequest.php index 35f69c7b71..5eb7851d50 100644 --- a/backend/app/Http/Request/Message/SendMessageRequest.php +++ b/backend/app/Http/Request/Message/SendMessageRequest.php @@ -17,9 +17,9 @@ public function rules(): array 'is_test' => 'boolean', 'attendee_ids' => 'max:50,array|required_if:message_type,' . MessageTypeEnum::ATTENDEE->name, 'attendee_ids.*' => 'integer', - 'ticket_ids' => ['array', 'required_if:message_type,' . MessageTypeEnum::TICKET->name], + 'product_ids' => ['array', 'required_if:message_type,' . MessageTypeEnum::PRODUCT->name], 'order_id' => 'integer|required_if:message_type,' . MessageTypeEnum::ORDER->name, - 'ticket_ids.*' => 'integer', + 'product_ids.*' => 'integer', ]; } @@ -28,7 +28,7 @@ public function messages(): array return [ 'subject.max' => 'The subject must be less than 100 characters.', 'attendee_ids.max' => 'You can only send a message to a maximum of 50 individual attendees at a time. ' . - 'To message more attendees, you can send to attendees with a specific ticket, or to all event attendees.' + 'To message more attendees, you can send to attendees with a specific product, or to all event attendees.' ]; } } diff --git a/backend/app/Http/Request/Product/SortProductsRequest.php b/backend/app/Http/Request/Product/SortProductsRequest.php new file mode 100644 index 0000000000..5c78b486b4 --- /dev/null +++ b/backend/app/Http/Request/Product/SortProductsRequest.php @@ -0,0 +1,19 @@ + 'array|required', + 'sorted_categories.*.product_category_id' => 'integer|required', + 'sorted_categories.*.sorted_products' => 'array', + 'sorted_categories.*.sorted_products.*.id' => 'integer|required', + 'sorted_categories.*.sorted_products.*.order' => 'integer', + ]; + } +} diff --git a/backend/app/Http/Request/Ticket/UpsertTicketRequest.php b/backend/app/Http/Request/Product/UpsertProductRequest.php similarity index 76% rename from backend/app/Http/Request/Ticket/UpsertTicketRequest.php rename to backend/app/Http/Request/Product/UpsertProductRequest.php index 4eff2df352..eb8f91b52a 100644 --- a/backend/app/Http/Request/Ticket/UpsertTicketRequest.php +++ b/backend/app/Http/Request/Product/UpsertProductRequest.php @@ -2,14 +2,15 @@ declare(strict_types=1); -namespace HiEvents\Http\Request\Ticket; +namespace HiEvents\Http\Request\Product; -use HiEvents\DomainObjects\Enums\TicketType; +use HiEvents\DomainObjects\Enums\ProductPriceType; +use HiEvents\DomainObjects\Enums\ProductType; use HiEvents\Http\Request\BaseRequest; use HiEvents\Validators\Rules\RulesHelper; use Illuminate\Validation\Rule; -class UpsertTicketRequest extends BaseRequest +class UpsertProductRequest extends BaseRequest { public function rules(): array { @@ -22,7 +23,7 @@ public function rules(): array 'max_per_order' => 'integer|nullable', 'prices' => ['required', 'array'], 'prices.*.price' => [...RulesHelper::MONEY, 'required'], - 'prices.*.label' => ['nullable', ...RulesHelper::STRING, 'required_if:type,' . TicketType::TIERED->name], + 'prices.*.label' => ['nullable', ...RulesHelper::STRING, 'required_if:type,' . ProductPriceType::TIERED->name], 'prices.*.sale_start_date' => ['date', 'nullable', 'after:sale_start_date'], 'prices.*.sale_end_date' => 'date|nullable|after:prices.*.sale_start_date', 'prices.*.initial_quantity_available' => ['integer', 'nullable', 'min:0'], @@ -36,8 +37,10 @@ public function rules(): array 'start_collapsed' => 'boolean', 'show_quantity_remaining' => 'boolean', 'is_hidden_without_promo_code' => 'boolean', - 'type' => ['required', Rule::in(TicketType::valuesArray())], + 'type' => ['required', Rule::in(ProductPriceType::valuesArray())], + 'product_type' => ['required', Rule::in(ProductType::valuesArray())], 'tax_and_fee_ids' => 'array', + 'product_category_id' => ['required', 'integer'], ]; } @@ -47,7 +50,8 @@ public function messages(): array 'sale_end_date.after' => __('The sale end date must be after the sale start date.'), 'prices.*.sale_end_date.after' => __('The sale end date must be after the sale start date.'), 'prices.*.sale_end_date.date' => __('The sale end date must be a valid date.'), - 'prices.*.sale_start_date.after' => __('The sale start date must be after the ticket sale start date.'), + 'prices.*.sale_start_date.after' => __('The sale start date must be after the product sale start date.'), + 'product_category_id.required' => __('You must select a product category.'), ]; } } diff --git a/backend/app/Http/Request/ProductCategory/UpsertProductCategoryRequest.php b/backend/app/Http/Request/ProductCategory/UpsertProductCategoryRequest.php new file mode 100644 index 0000000000..11e19e91a5 --- /dev/null +++ b/backend/app/Http/Request/ProductCategory/UpsertProductCategoryRequest.php @@ -0,0 +1,18 @@ + ['string', 'required', 'max:50'], + 'description' => ['string', 'max:255', 'nullable'], + 'is_hidden' => ['boolean', 'required'], + 'no_products_message' => ['string', 'max:255', 'required'], + ]; + } +} diff --git a/backend/app/Http/Request/PromoCode/CreateUpdatePromoCodeRequest.php b/backend/app/Http/Request/PromoCode/CreateUpdatePromoCodeRequest.php index 328fd0cbd1..5e381f6120 100644 --- a/backend/app/Http/Request/PromoCode/CreateUpdatePromoCodeRequest.php +++ b/backend/app/Http/Request/PromoCode/CreateUpdatePromoCodeRequest.php @@ -12,7 +12,7 @@ public function rules(): array { return [ 'code' => 'min:2|string|required|max:50', - 'applicable_ticket_ids' => 'array', + 'applicable_product_ids' => 'array', 'discount' => [ 'required_if:discount_type,PERCENTAGE,FIXED', 'numeric', diff --git a/backend/app/Http/Request/Questions/UpsertQuestionRequest.php b/backend/app/Http/Request/Questions/UpsertQuestionRequest.php index e696d15e3e..a383a0036d 100644 --- a/backend/app/Http/Request/Questions/UpsertQuestionRequest.php +++ b/backend/app/Http/Request/Questions/UpsertQuestionRequest.php @@ -15,9 +15,9 @@ public function rules(): array 'title' => ['string', 'required'], 'description' => ['string', 'nullable', 'max:10000'], 'type' => ['required', Rule::in(QuestionTypeEnum::valuesArray())], - 'ticket_ids' => ['array', 'required_if:belongs_to,TICKET'], + 'product_ids' => ['array', 'required_if:belongs_to,PRODUCT'], 'belongs_to' => [ - ['required', Rule::in([QuestionBelongsTo::TICKET->name, QuestionBelongsTo::ORDER->name])], + ['required', Rule::in([QuestionBelongsTo::PRODUCT->name, QuestionBelongsTo::ORDER->name])], ], 'options' => 'max:2000|required_if:type,CHECKBOX,RADIO', 'required' => 'required|boolean', @@ -28,7 +28,7 @@ public function rules(): array public function messages(): array { return [ - 'ticket_ids.required_if' => __('Please select at least one ticket.'), + 'product_ids.required_if' => __('Please select at least one product.'), ]; } } diff --git a/backend/app/Http/Request/Ticket/SortTicketsRequest.php b/backend/app/Http/Request/Ticket/SortTicketsRequest.php deleted file mode 100644 index 5ca72804f9..0000000000 --- a/backend/app/Http/Request/Ticket/SortTicketsRequest.php +++ /dev/null @@ -1,16 +0,0 @@ - 'integer|required', - '*.order' => 'integer|required', - ]; - } -} diff --git a/backend/app/Jobs/Order/SendOrderDetailsEmailJob.php b/backend/app/Jobs/Order/SendOrderDetailsEmailJob.php index c6fb63f948..4ca2fcf193 100644 --- a/backend/app/Jobs/Order/SendOrderDetailsEmailJob.php +++ b/backend/app/Jobs/Order/SendOrderDetailsEmailJob.php @@ -22,6 +22,6 @@ public function __construct(private readonly OrderDomainObject $order) public function handle(SendOrderDetailsService $service): void { - $service->sendOrderSummaryAndTicketEmails($this->order); + $service->sendOrderSummaryAndProductEmails($this->order); } } diff --git a/backend/app/Models/Attendee.php b/backend/app/Models/Attendee.php index 9164ce2361..476a6d1609 100644 --- a/backend/app/Models/Attendee.php +++ b/backend/app/Models/Attendee.php @@ -30,9 +30,9 @@ public function order(): BelongsTo return $this->belongsTo(Order::class); } - public function ticket(): BelongsTo + public function product(): BelongsTo { - return $this->belongsTo(Ticket::class); + return $this->belongsTo(Product::class); } public function check_in(): HasOne diff --git a/backend/app/Models/AttendeeCheckIn.php b/backend/app/Models/AttendeeCheckIn.php index be8f6f681f..50eff85135 100644 --- a/backend/app/Models/AttendeeCheckIn.php +++ b/backend/app/Models/AttendeeCheckIn.php @@ -16,10 +16,10 @@ protected function getFillableFields(): array return []; } - public function tickets(): BelongsTo + public function products(): BelongsTo { return $this->belongsTo( - related: Ticket::class, + related: Product::class, ); } diff --git a/backend/app/Models/CapacityAssignment.php b/backend/app/Models/CapacityAssignment.php index 7359f9455e..87518f133b 100644 --- a/backend/app/Models/CapacityAssignment.php +++ b/backend/app/Models/CapacityAssignment.php @@ -22,11 +22,11 @@ public function event(): BelongsTo return $this->belongsTo(Event::class); } - public function tickets(): BelongsToMany + public function products(): BelongsToMany { return $this->belongsToMany( - related: Ticket::class, - table: 'ticket_capacity_assignments', + related: Product::class, + table: 'product_capacity_assignments', ); } } diff --git a/backend/app/Models/CheckInList.php b/backend/app/Models/CheckInList.php index d61c62b989..0e918372e1 100644 --- a/backend/app/Models/CheckInList.php +++ b/backend/app/Models/CheckInList.php @@ -17,11 +17,11 @@ protected function getFillableFields(): array return []; } - public function tickets(): BelongsToMany + public function products(): BelongsToMany { return $this->belongsToMany( - related: Ticket::class, - table: 'ticket_check_in_lists', + related: Product::class, + table: 'product_check_in_lists', ); } diff --git a/backend/app/Models/Event.php b/backend/app/Models/Event.php index d7be878910..d87972ee60 100644 --- a/backend/app/Models/Event.php +++ b/backend/app/Models/Event.php @@ -19,9 +19,14 @@ public function organizer(): BelongsTo return $this->belongsTo(Organizer::class); } - public function tickets(): HasMany + public function products(): HasMany { - return $this->hasMany(Ticket::class)->orderBy('order'); + return $this->hasMany(Product::class)->orderBy('order'); + } + + public function product_categories(): HasMany + { + return $this->hasMany(ProductCategory::class)->orderBy('order'); } public function attendees(): HasMany diff --git a/backend/app/Models/Message.php b/backend/app/Models/Message.php index 7e08f7aed7..1cac8fae37 100644 --- a/backend/app/Models/Message.php +++ b/backend/app/Models/Message.php @@ -15,7 +15,7 @@ protected function getCastMap(): array { return [ 'attendee_ids' => 'array', - 'ticket_ids' => 'array', + 'product_ids' => 'array', 'send_data' => 'array', ]; } diff --git a/backend/app/Models/OrderItem.php b/backend/app/Models/OrderItem.php index b1b87bd985..cd5796707f 100644 --- a/backend/app/Models/OrderItem.php +++ b/backend/app/Models/OrderItem.php @@ -36,13 +36,13 @@ protected function getFillableFields(): array return []; } - public function ticket_price(): HasOne + public function product_price(): HasOne { - return $this->hasOne(TicketPrice::class); + return $this->hasOne(ProductPrice::class); } - public function ticket(): BelongsTo + public function product(): BelongsTo { - return $this->belongsTo(Ticket::class); + return $this->belongsTo(Product::class); } } diff --git a/backend/app/Models/Product.php b/backend/app/Models/Product.php new file mode 100644 index 0000000000..a820857821 --- /dev/null +++ b/backend/app/Models/Product.php @@ -0,0 +1,56 @@ + 'float', + ProductDomainObjectAbstract::SALES_TAX_VOLUME => 'float', + ]; + } + + protected function getFillableFields(): array + { + return []; + } + + public function questions(): BelongsToMany + { + return $this->belongsToMany(Question::class, 'product_questions'); + } + + public function product_prices(): HasMany + { + return $this->hasMany(ProductPrice::class)->orderBy('order'); + } + + public function tax_and_fees(): BelongsToMany + { + return $this->belongsToMany(TaxAndFee::class, 'product_taxes_and_fees'); + } + + public function capacity_assignments(): BelongsToMany + { + return $this->belongsToMany(CapacityAssignment::class, 'product_capacity_assignments'); + } + + public function check_in_lists(): BelongsToMany + { + return $this->belongsToMany(CheckInList::class, 'product_check_in_lists'); + } + + public function product_category(): BelongsTo + { + return $this->belongsTo(ProductCategory::class); + } +} diff --git a/backend/app/Models/ProductCategory.php b/backend/app/Models/ProductCategory.php new file mode 100644 index 0000000000..b96826a21c --- /dev/null +++ b/backend/app/Models/ProductCategory.php @@ -0,0 +1,35 @@ +hasMany(Product::class); + } +} diff --git a/backend/app/Models/TicketPrice.php b/backend/app/Models/ProductPrice.php similarity index 70% rename from backend/app/Models/TicketPrice.php rename to backend/app/Models/ProductPrice.php index 1ac6e299e1..23ab15a389 100644 --- a/backend/app/Models/TicketPrice.php +++ b/backend/app/Models/ProductPrice.php @@ -4,7 +4,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; -class TicketPrice extends BaseModel +class ProductPrice extends BaseModel { protected function getCastMap(): array { @@ -18,8 +18,8 @@ protected function getFillableFields(): array return []; } - public function ticket(): BelongsTo + public function product(): BelongsTo { - return $this->belongsTo(Ticket::class); + return $this->belongsTo(Product::class); } } diff --git a/backend/app/Models/TicketQuestion.php b/backend/app/Models/ProductQuestion.php similarity index 55% rename from backend/app/Models/TicketQuestion.php rename to backend/app/Models/ProductQuestion.php index 4c85d75c10..fee35fc522 100644 --- a/backend/app/Models/TicketQuestion.php +++ b/backend/app/Models/ProductQuestion.php @@ -2,9 +2,9 @@ namespace HiEvents\Models; -use HiEvents\DomainObjects\Generated\TicketQuestionDomainObjectAbstract; +use HiEvents\DomainObjects\Generated\ProductQuestionDomainObjectAbstract; -class TicketQuestion extends BaseModel +class ProductQuestion extends BaseModel { protected function getTimestampsEnabled(): bool { @@ -19,8 +19,8 @@ protected function getCastMap(): array protected function getFillableFields(): array { return [ - TicketQuestionDomainObjectAbstract::QUESTION_ID, - TicketQuestionDomainObjectAbstract::TICKET_ID, + ProductQuestionDomainObjectAbstract::QUESTION_ID, + ProductQuestionDomainObjectAbstract::PRODUCT_ID, ]; } } diff --git a/backend/app/Models/PromoCode.php b/backend/app/Models/PromoCode.php index d6d7545afb..1e0ad4f0c5 100644 --- a/backend/app/Models/PromoCode.php +++ b/backend/app/Models/PromoCode.php @@ -11,7 +11,7 @@ protected function getCastMap(): array return [ PromoCodeDomainObjectAbstract::DISCOUNT => 'float', PromoCodeDomainObjectAbstract::EXPIRY_DATE => 'datetime', - PromoCodeDomainObjectAbstract::APPLICABLE_TICKET_IDS => 'array', + PromoCodeDomainObjectAbstract::APPLICABLE_PRODUCT_IDS => 'array', ]; } @@ -21,7 +21,7 @@ protected function getFillableFields(): array PromoCodeDomainObjectAbstract::CODE, PromoCodeDomainObjectAbstract::DISCOUNT, PromoCodeDomainObjectAbstract::DISCOUNT_TYPE, - PromoCodeDomainObjectAbstract::APPLICABLE_TICKET_IDS, + PromoCodeDomainObjectAbstract::APPLICABLE_PRODUCT_IDS, PromoCodeDomainObjectAbstract::EXPIRY_DATE, PromoCodeDomainObjectAbstract::EVENT_ID, PromoCodeDomainObjectAbstract::MAX_ALLOWED_USAGES, diff --git a/backend/app/Models/Question.php b/backend/app/Models/Question.php index 1fa2744f8d..f540466f5a 100644 --- a/backend/app/Models/Question.php +++ b/backend/app/Models/Question.php @@ -19,10 +19,10 @@ protected function getFillableFields(): array return []; } - public function tickets(): BelongsToMany + public function products(): BelongsToMany { return $this - ->belongsToMany(Ticket::class, 'ticket_questions') - ->whereNull('ticket_questions.deleted_at'); + ->belongsToMany(Product::class, 'product_questions') + ->whereNull('product_questions.deleted_at'); } } diff --git a/backend/app/Models/QuestionAnswer.php b/backend/app/Models/QuestionAnswer.php index 3564d22ce5..c9c4de1313 100644 --- a/backend/app/Models/QuestionAnswer.php +++ b/backend/app/Models/QuestionAnswer.php @@ -17,7 +17,7 @@ protected function getFillableFields(): array { return [ QuestionAnswerDomainObjectAbstract::QUESTION_ID, - QuestionAnswerDomainObjectAbstract::TICKET_ID, + QuestionAnswerDomainObjectAbstract::PRODUCT_ID, QuestionAnswerDomainObjectAbstract::ORDER_ID, QuestionAnswerDomainObjectAbstract::ATTENDEE_ID, QuestionAnswerDomainObjectAbstract::ANSWER, diff --git a/backend/app/Models/TaxAndFee.php b/backend/app/Models/TaxAndFee.php index 32a656b9a5..1d36edbe80 100644 --- a/backend/app/Models/TaxAndFee.php +++ b/backend/app/Models/TaxAndFee.php @@ -8,9 +8,9 @@ class TaxAndFee extends BaseModel { protected $table = 'taxes_and_fees'; - public function tickets(): BelongsToMany + public function products(): BelongsToMany { - return $this->belongsToMany(Ticket::class, 'ticket_taxes_and_fees'); + return $this->belongsToMany(Product::class, 'product_taxes_and_fees'); } protected function getCastMap(): array diff --git a/backend/app/Models/Ticket.php b/backend/app/Models/Ticket.php deleted file mode 100644 index dea99092f3..0000000000 --- a/backend/app/Models/Ticket.php +++ /dev/null @@ -1,50 +0,0 @@ - 'float', - TicketDomainObjectAbstract::SALES_TAX_VOLUME => 'float', - ]; - } - - protected function getFillableFields(): array - { - return []; - } - - public function questions(): BelongsToMany - { - return $this->belongsToMany(Question::class, 'ticket_questions'); - } - - public function ticket_prices(): HasMany - { - return $this->hasMany(TicketPrice::class)->orderBy('order'); - } - - public function tax_and_fees(): BelongsToMany - { - return $this->belongsToMany(TaxAndFee::class, 'ticket_taxes_and_fees'); - } - - public function capacity_assignments(): BelongsToMany - { - return $this->belongsToMany(CapacityAssignment::class, 'ticket_capacity_assignments'); - } - - public function check_in_lists(): BelongsToMany - { - return $this->belongsToMany(CheckInList::class, 'ticket_check_in_lists'); - } -} diff --git a/backend/app/Providers/RepositoryServiceProvider.php b/backend/app/Providers/RepositoryServiceProvider.php index 2ae3adc901..046f41a36b 100644 --- a/backend/app/Providers/RepositoryServiceProvider.php +++ b/backend/app/Providers/RepositoryServiceProvider.php @@ -21,14 +21,15 @@ use HiEvents\Repository\Eloquent\OrganizerRepository; use HiEvents\Repository\Eloquent\PasswordResetRepository; use HiEvents\Repository\Eloquent\PasswordResetTokenRepository; +use HiEvents\Repository\Eloquent\ProductCategoryRepository; use HiEvents\Repository\Eloquent\PromoCodeRepository; use HiEvents\Repository\Eloquent\QuestionAnswerRepository; use HiEvents\Repository\Eloquent\QuestionRepository; use HiEvents\Repository\Eloquent\StripeCustomerRepository; use HiEvents\Repository\Eloquent\StripePaymentsRepository; use HiEvents\Repository\Eloquent\TaxAndFeeRepository; -use HiEvents\Repository\Eloquent\TicketPriceRepository; -use HiEvents\Repository\Eloquent\TicketRepository; +use HiEvents\Repository\Eloquent\ProductPriceRepository; +use HiEvents\Repository\Eloquent\ProductRepository; use HiEvents\Repository\Eloquent\UserRepository; use HiEvents\Repository\Interfaces\AccountRepositoryInterface; use HiEvents\Repository\Interfaces\AccountUserRepositoryInterface; @@ -47,14 +48,15 @@ use HiEvents\Repository\Interfaces\OrganizerRepositoryInterface; use HiEvents\Repository\Interfaces\PasswordResetRepositoryInterface; use HiEvents\Repository\Interfaces\PasswordResetTokenRepositoryInterface; +use HiEvents\Repository\Interfaces\ProductCategoryRepositoryInterface; use HiEvents\Repository\Interfaces\PromoCodeRepositoryInterface; use HiEvents\Repository\Interfaces\QuestionAnswerRepositoryInterface; use HiEvents\Repository\Interfaces\QuestionRepositoryInterface; use HiEvents\Repository\Interfaces\StripeCustomerRepositoryInterface; use HiEvents\Repository\Interfaces\StripePaymentsRepositoryInterface; use HiEvents\Repository\Interfaces\TaxAndFeeRepositoryInterface; -use HiEvents\Repository\Interfaces\TicketPriceRepositoryInterface; -use HiEvents\Repository\Interfaces\TicketRepositoryInterface; +use HiEvents\Repository\Interfaces\ProductPriceRepositoryInterface; +use HiEvents\Repository\Interfaces\ProductRepositoryInterface; use HiEvents\Repository\Interfaces\UserRepositoryInterface; use Illuminate\Support\ServiceProvider; @@ -67,7 +69,7 @@ class RepositoryServiceProvider extends ServiceProvider UserRepositoryInterface::class => UserRepository::class, AccountRepositoryInterface::class => AccountRepository::class, EventRepositoryInterface::class => EventRepository::class, - TicketRepositoryInterface::class => TicketRepository::class, + ProductRepositoryInterface::class => ProductRepository::class, OrderRepositoryInterface::class => OrderRepository::class, AttendeeRepositoryInterface::class => AttendeeRepository::class, OrderItemRepositoryInterface::class => OrderItemRepository::class, @@ -80,7 +82,7 @@ class RepositoryServiceProvider extends ServiceProvider PasswordResetRepositoryInterface::class => PasswordResetRepository::class, TaxAndFeeRepositoryInterface::class => TaxAndFeeRepository::class, ImageRepositoryInterface::class => ImageRepository::class, - TicketPriceRepositoryInterface::class => TicketPriceRepository::class, + ProductPriceRepositoryInterface::class => ProductPriceRepository::class, EventStatisticRepositoryInterface::class => EventStatisticRepository::class, EventDailyStatisticRepositoryInterface::class => EventDailyStatisticRepository::class, EventSettingsRepositoryInterface::class => EventSettingsRepository::class, @@ -90,6 +92,7 @@ class RepositoryServiceProvider extends ServiceProvider StripeCustomerRepositoryInterface::class => StripeCustomerRepository::class, CheckInListRepositoryInterface::class => CheckInListRepository::class, AttendeeCheckInRepositoryInterface::class => AttendeeCheckInRepository::class, + ProductCategoryRepositoryInterface::class => ProductCategoryRepository::class, ]; public function register(): void diff --git a/backend/app/Repository/Eloquent/AttendeeRepository.php b/backend/app/Repository/Eloquent/AttendeeRepository.php index a60f3439f9..e0e16e381e 100644 --- a/backend/app/Repository/Eloquent/AttendeeRepository.php +++ b/backend/app/Repository/Eloquent/AttendeeRepository.php @@ -108,8 +108,8 @@ public function getAttendeesByCheckInShortId(string $shortId, QueryParamsDTO $pa $this->model = $this->model->select('attendees.*') ->join('orders', 'orders.id', '=', 'attendees.order_id') - ->join('ticket_check_in_lists', 'ticket_check_in_lists.ticket_id', '=', 'attendees.ticket_id') - ->join('check_in_lists', 'check_in_lists.id', '=', 'ticket_check_in_lists.check_in_list_id') + ->join('product_check_in_lists', 'product_check_in_lists.product_id', '=', 'attendees.product_id') + ->join('check_in_lists', 'check_in_lists.id', '=', 'product_check_in_lists.check_in_list_id') ->where('check_in_lists.short_id', $shortId) ->where('attendees.status', AttendeeStatus::ACTIVE->name) ->whereIn('orders.status', [OrderStatus::COMPLETED->name]); diff --git a/backend/app/Repository/Eloquent/BaseRepository.php b/backend/app/Repository/Eloquent/BaseRepository.php index c781a44bf3..1be0bc0534 100644 --- a/backend/app/Repository/Eloquent/BaseRepository.php +++ b/backend/app/Repository/Eloquent/BaseRepository.php @@ -128,10 +128,25 @@ public function findFirst(int $id, array $columns = self::DEFAULT_COLUMNS): ?Dom return $this->handleSingleResult($this->model->findOrFail($id, $columns)); } - public function findWhere(array $where, array $columns = self::DEFAULT_COLUMNS): Collection + public function findWhere( + array $where, + array $columns = self::DEFAULT_COLUMNS, + array $orderAndDirections = [], + ): Collection { $this->applyConditions($where); + + if ($orderAndDirections) { + foreach ($orderAndDirections as $orderAndDirection) { + $this->model = $this->model->orderBy( + $orderAndDirection->getOrder(), + $orderAndDirection->getDirection() + ); + } + } + $model = $this->model->get($columns); + $this->resetModel(); return $this->handleResults($model); diff --git a/backend/app/Repository/Eloquent/CheckInListRepository.php b/backend/app/Repository/Eloquent/CheckInListRepository.php index 45d804461c..47baa4e7fa 100644 --- a/backend/app/Repository/Eloquent/CheckInListRepository.php +++ b/backend/app/Repository/Eloquent/CheckInListRepository.php @@ -38,7 +38,7 @@ public function getCheckedInAttendeeCountById(int $checkInListId): CheckedInAtte valid_attendees AS ( SELECT a.id, tcil.check_in_list_id FROM attendees a - JOIN ticket_check_in_lists tcil ON a.ticket_id = tcil.ticket_id + JOIN product_check_in_lists tcil ON a.product_id = tcil.product_id WHERE a.deleted_at IS NULL AND tcil.deleted_at IS NULL ) @@ -78,7 +78,7 @@ public function getCheckedInAttendeeCountByIds(array $checkInListIds): Collectio valid_attendees AS ( SELECT a.id, tcil.check_in_list_id FROM attendees a - JOIN ticket_check_in_lists tcil ON a.ticket_id = tcil.ticket_id + JOIN product_check_in_lists tcil ON a.product_id = tcil.product_id WHERE a.deleted_at IS NULL AND tcil.deleted_at IS NULL AND a.status = '$attendeeActiveStatus' diff --git a/backend/app/Repository/Eloquent/ProductCategoryRepository.php b/backend/app/Repository/Eloquent/ProductCategoryRepository.php new file mode 100644 index 0000000000..6a1546c471 --- /dev/null +++ b/backend/app/Repository/Eloquent/ProductCategoryRepository.php @@ -0,0 +1,50 @@ +model + ->where('event_id', $eventId) + ->with(['products']); + + // Apply filters from QueryParamsDTO, if needed + if (!empty($queryParamsDTO->filter_fields)) { + foreach ($queryParamsDTO->filter_fields as $filter) { + $query->where($filter->field, $filter->operator ?? '=', $filter->value); + } + } + + // Apply sorting from QueryParamsDTO + if (!empty($queryParamsDTO->sort_by)) { + $query->orderBy($queryParamsDTO->sort_by, $queryParamsDTO->sort_direction ?? 'asc'); + } + + return $query->get(); + } + + public function getNextOrder(int $eventId) + { + return $this->model + ->where('event_id', $eventId) + ->max('order') + 1; + } +} diff --git a/backend/app/Repository/Eloquent/ProductPriceRepository.php b/backend/app/Repository/Eloquent/ProductPriceRepository.php new file mode 100644 index 0000000000..7114d6f3bc --- /dev/null +++ b/backend/app/Repository/Eloquent/ProductPriceRepository.php @@ -0,0 +1,20 @@ +query)) { + $where[] = static function (Builder $builder) use ($params) { + $builder + ->where(ProductDomainObjectAbstract::TITLE, 'ilike', '%' . $params->query . '%'); + }; + } + + $this->model = $this->model->orderBy( + $params->sort_by ?? ProductDomainObject::getDefaultSort(), + $params->sort_direction ?? ProductDomainObject::getDefaultSortDirection(), + ); + + return $this->paginateWhere( + where: $where, + limit: $params->per_page, + page: $params->page, + ); + } + + /** + * @param int $productId + * @param int $productPriceId + * @return int + */ + public function getQuantityRemainingForProductPrice(int $productId, int $productPriceId): int + { + $query = <<db->selectOne($query, [ + 'productPriceId' => $productPriceId, + 'productId' => $productId + ]); + + if ($result === null) { + throw new RuntimeException('Product price not found'); + } + + if ($result->unlimited_products_available) { + return Constants::INFINITE; + } + + return (int)$result->quantity_remaining; + } + + public function getTaxesByProductId(int $productId): Collection + { + $query = <<db->select($query, [ + 'productId' => $productId + ]); + + return $this->handleResults($taxAndFees, TaxAndFeesDomainObject::class); + } + + public function getProductsByTaxId(int $taxId): Collection + { + $query = <<model->select($query, [ + 'taxAndFeeId' => $taxId + ]); + + return $this->handleResults($products, ProductDomainObject::class); + } + + public function getCapacityAssignmentsByProductId(int $productId): Collection + { + $capacityAssignments = CapacityAssignment::whereHas('products', static function ($query) use ($productId) { + $query->where('product_id', $productId); + })->get(); + + return $this->handleResults($capacityAssignments, CapacityAssignmentDomainObject::class); + } + + public function addTaxesAndFeesToProduct(int $productId, array $taxIds): void + { + Product::findOrFail($productId)?->tax_and_fees()->sync($taxIds); + } + + public function addCapacityAssignmentToProducts(int $capacityAssignmentId, array $productIds): void + { + $productIds = array_unique($productIds); + + Product::whereNotIn('id', $productIds) + ->whereHas('capacity_assignments', function ($query) use ($capacityAssignmentId) { + $query->where('capacity_assignment_id', $capacityAssignmentId); + }) + ->each(function (Product $product) use ($capacityAssignmentId) { + $product->capacity_assignments()->detach($capacityAssignmentId); + }); + + Product::whereIn('id', $productIds) + ->each(function (Product $product) use ($capacityAssignmentId) { + $product->capacity_assignments()->syncWithoutDetaching([$capacityAssignmentId]); + }); + } + + public function addCheckInListToProducts(int $checkInListId, array $productIds): void + { + $productIds = array_unique($productIds); + + Product::whereNotIn('id', $productIds) + ->whereHas('check_in_lists', function ($query) use ($checkInListId) { + $query->where('check_in_list_id', $checkInListId); + }) + ->each(function (Product $product) use ($checkInListId) { + $product->check_in_lists()->detach($checkInListId); + }); + + Product::whereIn('id', $productIds) + ->each(function (Product $product) use ($checkInListId) { + $product->check_in_lists()->syncWithoutDetaching([$checkInListId]); + }); + } + + public function removeCheckInListFromProducts(int $checkInListId): void + { + $checkInList = CheckInList::find($checkInListId); + + $checkInList?->products()->detach(); + } + + public function removeCapacityAssignmentFromProducts(int $capacityAssignmentId): void + { + $capacityAssignment = CapacityAssignment::find($capacityAssignmentId); + + $capacityAssignment?->products()->detach(); + } + + /** + * @throws Throwable + */ + public function bulkUpdateProductsAndCategories(int $eventId, array $productUpdates, array $categoryUpdates): void + { + $this->db->beginTransaction(); + + try { + $productIds = array_column($productUpdates, 'id'); + $productOrders = range(1, count($productUpdates)); + $productCategoryIds = array_column($productUpdates, 'product_category_id'); + + $productParameters = [ + 'eventId' => $eventId, + 'productIds' => '{' . implode(',', $productIds) . '}', + 'productOrders' => '{' . implode(',', $productOrders) . '}', + 'productCategoryIds' => '{' . implode(',', $productCategoryIds) . '}', + ]; + + $productUpdateQuery = "WITH new_order AS ( + SELECT unnest(:productIds::bigint[]) AS product_id, + unnest(:productOrders::int[]) AS order, + unnest(:productCategoryIds::bigint[]) AS category_id + ) + UPDATE products + SET \"order\" = new_order.order, + product_category_id = new_order.category_id, + updated_at = NOW() + FROM new_order + WHERE products.id = new_order.product_id AND products.event_id = :eventId"; + + $this->db->update($productUpdateQuery, $productParameters); + + $categoryIds = array_column($categoryUpdates, 'id'); + $categoryOrders = array_column($categoryUpdates, 'order'); + + $categoryParameters = [ + 'eventId' => $eventId, + 'categoryIds' => '{' . implode(',', $categoryIds) . '}', + 'categoryOrders' => '{' . implode(',', $categoryOrders) . '}', + ]; + + $categoryUpdateQuery = "WITH new_category_order AS ( + SELECT unnest(:categoryIds::bigint[]) AS category_id, + unnest(:categoryOrders::int[]) AS order + ) + UPDATE product_categories + SET \"order\" = new_category_order.order, + updated_at = NOW() + FROM new_category_order + WHERE product_categories.id = new_category_order.category_id AND product_categories.event_id = :eventId"; + + $this->db->update($categoryUpdateQuery, $categoryParameters); + + $this->db->commit(); + } catch (Exception $e) { + $this->db->rollBack(); + throw $e; + } + } + + public function hasAssociatedOrders(int $productId): bool + { + return $this->db->table('order_items') + ->join('orders', 'order_items.order_id', '=', 'orders.id') + ->whereIn('orders.status', [OrderStatus::COMPLETED->name, OrderStatus::CANCELLED->name]) + ->where('order_items.product_id', $productId) + ->exists(); + } + + public function getModel(): string + { + return Product::class; + } + + public function getDomainObject(): string + { + return ProductDomainObject::class; + } +} diff --git a/backend/app/Repository/Eloquent/QuestionRepository.php b/backend/app/Repository/Eloquent/QuestionRepository.php index 8c2e1e9a0d..1e0dcdbb60 100644 --- a/backend/app/Repository/Eloquent/QuestionRepository.php +++ b/backend/app/Repository/Eloquent/QuestionRepository.php @@ -5,21 +5,21 @@ use HiEvents\DomainObjects\Generated\QuestionDomainObjectAbstract; use HiEvents\DomainObjects\QuestionDomainObject; use HiEvents\Models\Question; -use HiEvents\Models\TicketQuestion; +use HiEvents\Models\ProductQuestion; use HiEvents\Repository\Interfaces\QuestionRepositoryInterface; -use HiEvents\Repository\Interfaces\TicketRepositoryInterface; +use HiEvents\Repository\Interfaces\ProductRepositoryInterface; use Illuminate\Database\DatabaseManager; use Illuminate\Foundation\Application; use Illuminate\Support\Collection; class QuestionRepository extends BaseRepository implements QuestionRepositoryInterface { - private TicketRepositoryInterface $ticketRepository; + private ProductRepositoryInterface $productRepository; - public function __construct(Application $application, DatabaseManager $db, TicketRepositoryInterface $ticketRepository) + public function __construct(Application $application, DatabaseManager $db, ProductRepositoryInterface $productRepository) { parent::__construct($application, $db); - $this->ticketRepository = $ticketRepository; + $this->productRepository = $productRepository; } protected function getModel(): string @@ -32,37 +32,37 @@ public function getDomainObject(): string return QuestionDomainObject::class; } - public function create(array $attributes, array $ticketIds = []): QuestionDomainObject + public function create(array $attributes, array $productIds = []): QuestionDomainObject { /** @var QuestionDomainObject $question */ $question = parent::create($attributes); - foreach ($ticketIds as $ticketId) { - $ticketQuestion = new TicketQuestion(); - $ticketQuestion->create([ - 'ticket_id' => $ticketId, + foreach ($productIds as $productId) { + $productQuestion = new ProductQuestion(); + $productQuestion->create([ + 'product_id' => $productId, 'question_id' => $question->getId(), ]); } - $question->setTickets($this->ticketRepository->findWhereIn('id', $ticketIds)); + $question->setProducts($this->productRepository->findWhereIn('id', $productIds)); return $question; } - public function updateQuestion(int $questionId, int $eventId, array $attributes, array $ticketIds = []): void + public function updateQuestion(int $questionId, int $eventId, array $attributes, array $productIds = []): void { $this->updateWhere($attributes, [ 'id' => $questionId, 'event_id' => $eventId, ]); - TicketQuestion::where('question_id', $questionId)->delete(); + ProductQuestion::where('question_id', $questionId)->delete(); - foreach ($ticketIds as $ticketId) { - $ticketQuestion = new TicketQuestion(); - $ticketQuestion->create([ - 'ticket_id' => $ticketId, + foreach ($productIds as $productId) { + $productQuestion = new ProductQuestion(); + $productQuestion->create([ + 'product_id' => $productId, 'question_id' => $questionId, ]); } diff --git a/backend/app/Repository/Eloquent/TicketPriceRepository.php b/backend/app/Repository/Eloquent/TicketPriceRepository.php deleted file mode 100644 index 9f40580138..0000000000 --- a/backend/app/Repository/Eloquent/TicketPriceRepository.php +++ /dev/null @@ -1,20 +0,0 @@ -query)) { - $where[] = static function (Builder $builder) use ($params) { - $builder - ->where(TicketDomainObjectAbstract::TITLE, 'ilike', '%' . $params->query . '%'); - }; - } - - $this->model = $this->model->orderBy( - $params->sort_by ?? TicketDomainObject::getDefaultSort(), - $params->sort_direction ?? TicketDomainObject::getDefaultSortDirection(), - ); - - return $this->paginateWhere( - where: $where, - limit: $params->per_page, - page: $params->page, - ); - } - - /** - * @param int $ticketId - * @param int $ticketPriceId - * @return int - */ - public function getQuantityRemainingForTicketPrice(int $ticketId, int $ticketPriceId): int - { - $query = <<db->selectOne($query, [ - 'ticketPriceId' => $ticketPriceId, - 'ticketId' => $ticketId - ]); - - if ($result === null) { - throw new RuntimeException('Ticket price not found'); - } - - if ($result->unlimited_tickets_available) { - return Constants::INFINITE; - } - - return (int)$result->quantity_remaining; - } - - public function getTaxesByTicketId(int $ticketId): Collection - { - $query = <<db->select($query, [ - 'ticketId' => $ticketId - ]); - - return $this->handleResults($taxAndFees, TaxAndFeesDomainObject::class); - } - - public function getTicketsByTaxId(int $taxId): Collection - { - $query = <<model->select($query, [ - 'taxAndFeeId' => $taxId - ]); - - return $this->handleResults($tickets, TicketDomainObject::class); - } - - public function getCapacityAssignmentsByTicketId(int $ticketId): Collection - { - $capacityAssignments = CapacityAssignment::whereHas('tickets', static function ($query) use ($ticketId) { - $query->where('ticket_id', $ticketId); - })->get(); - - return $this->handleResults($capacityAssignments, CapacityAssignmentDomainObject::class); - } - - public function addTaxesAndFeesToTicket(int $ticketId, array $taxIds): void - { - Ticket::findOrFail($ticketId)?->tax_and_fees()->sync($taxIds); - } - - public function addCapacityAssignmentToTickets(int $capacityAssignmentId, array $ticketIds): void - { - $ticketIds = array_unique($ticketIds); - - Ticket::whereNotIn('id', $ticketIds) - ->whereHas('capacity_assignments', function ($query) use ($capacityAssignmentId) { - $query->where('capacity_assignment_id', $capacityAssignmentId); - }) - ->each(function (Ticket $ticket) use ($capacityAssignmentId) { - $ticket->capacity_assignments()->detach($capacityAssignmentId); - }); - - Ticket::whereIn('id', $ticketIds) - ->each(function (Ticket $ticket) use ($capacityAssignmentId) { - $ticket->capacity_assignments()->syncWithoutDetaching([$capacityAssignmentId]); - }); - } - - public function addCheckInListToTickets(int $checkInListId, array $ticketIds): void - { - $ticketIds = array_unique($ticketIds); - - Ticket::whereNotIn('id', $ticketIds) - ->whereHas('check_in_lists', function ($query) use ($checkInListId) { - $query->where('check_in_list_id', $checkInListId); - }) - ->each(function (Ticket $ticket) use ($checkInListId) { - $ticket->check_in_lists()->detach($checkInListId); - }); - - Ticket::whereIn('id', $ticketIds) - ->each(function (Ticket $ticket) use ($checkInListId) { - $ticket->check_in_lists()->syncWithoutDetaching([$checkInListId]); - }); - } - - public function removeCheckInListFromTickets(int $checkInListId): void - { - $checkInList = CheckInList::find($checkInListId); - - $checkInList?->tickets()->detach(); - } - - public function removeCapacityAssignmentFromTickets(int $capacityAssignmentId): void - { - $capacityAssignment = CapacityAssignment::find($capacityAssignmentId); - - $capacityAssignment?->tickets()->detach(); - } - - public function sortTickets(int $eventId, array $orderedTicketIds): void - { - $parameters = [ - 'eventId' => $eventId, - 'ticketIds' => '{' . implode(',', $orderedTicketIds) . '}', - 'orders' => '{' . implode(',', range(1, count($orderedTicketIds))) . '}', - ]; - - $query = "WITH new_order AS ( - SELECT unnest(:ticketIds::bigint[]) AS ticket_id, - unnest(:orders::int[]) AS order - ) - UPDATE tickets - SET \"order\" = new_order.order - FROM new_order - WHERE tickets.id = new_order.ticket_id AND tickets.event_id = :eventId"; - - $this->db->update($query, $parameters); - } - - public function getModel(): string - { - return Ticket::class; - } - - public function getDomainObject(): string - { - return TicketDomainObject::class; - } -} diff --git a/backend/app/Repository/Eloquent/Value/OrderAndDirection.php b/backend/app/Repository/Eloquent/Value/OrderAndDirection.php new file mode 100644 index 0000000000..4a6058efaa --- /dev/null +++ b/backend/app/Repository/Eloquent/Value/OrderAndDirection.php @@ -0,0 +1,36 @@ +validate(); + } + + public function getOrder(): string + { + return $this->order; + } + + public function getDirection(): string + { + return $this->direction; + } + + private function validate(): void + { + if (!in_array($this->direction, ['asc', 'desc'])) { + throw new InvalidArgumentException(__('Invalid direction. Must be either asc or desc')); + } + } +} diff --git a/backend/app/Repository/Eloquent/Value/Relationship.php b/backend/app/Repository/Eloquent/Value/Relationship.php index 857173cae3..6dfcb7613c 100644 --- a/backend/app/Repository/Eloquent/Value/Relationship.php +++ b/backend/app/Repository/Eloquent/Value/Relationship.php @@ -2,18 +2,27 @@ namespace HiEvents\Repository\Eloquent\Value; -readonly class Relationship +use HiEvents\DomainObjects\Interfaces\DomainObjectInterface; +use InvalidArgumentException; + +class Relationship { public function __construct( - private string $domainObject, + private readonly string $domainObject, /** * @var Relationship[]|null */ - private ?array $nested = [], + private readonly ?array $nested = [], + + private readonly ?string $name = null, - private ?string $name = null, + /** + * @var OrderAndDirection[] + */ + private readonly array $orderAndDirections = [], ) { + $this->validate(); } public function getName(): string @@ -31,13 +40,23 @@ public function getDomainObject(): string return $this->domainObject; } + public function getOrderAndDirections(): array + { + return $this->orderAndDirections; + } + public function buildLaravelEagerLoadArray(): array { - if (!$this->nested) { - return [$this->getName()]; + $results = [ + $this->getName() => $this->buildOrderAndDirectionEloquentCallback() + ]; + + // If there are nested relationships, build them and merge into the results array + if ($this->nested) { + $results = array_merge($results, $this->buildNested($this, '')); } - return $this->buildNested($this, ''); + return $results; } private function buildNested(Relationship $relationship, string $prefix): array @@ -47,11 +66,51 @@ private function buildNested(Relationship $relationship, string $prefix): array if ($relationship->nested) { foreach ($relationship->nested as $nested) { $nestedPrefix = $prefix === '' ? $relationship->getName() : $prefix . '.' . $relationship->getName(); - $results[] = $nestedPrefix . '.' . $nested->getName(); + $results[$nestedPrefix . '.' . $nested->getName()] = $nested->buildOrderAndDirectionEloquentCallback(); $results = array_merge($results, $this->buildNested($nested, $nestedPrefix)); } } return $results; } + + private function buildOrderAndDirectionEloquentCallback(): callable|array + { + if ($this->getOrderAndDirections() === []) { + return []; + } + + return function ($query) { + foreach ($this->orderAndDirections as $orderAndDirection) { + $query->orderBy($orderAndDirection->getOrder(), $orderAndDirection->getDirection()); + } + }; + } + + private function validate(): void + { + if (!is_subclass_of($this->domainObject, DomainObjectInterface::class)) { + throw new InvalidArgumentException( + __('DomainObject must be a valid :interface.', [ + 'interface' => DomainObjectInterface::class, + ]), + ); + } + + foreach ($this->nested as $nested) { + if (!is_a($nested, __CLASS__)) { + throw new InvalidArgumentException( + __('Nested relationships must be an array of Relationship objects.'), + ); + } + } + + foreach ($this->orderAndDirections as $orderAndDirection) { + if (!is_a($orderAndDirection, OrderAndDirection::class)) { + throw new InvalidArgumentException( + __('OrderAndDirections must be an array of OrderAndDirection objects.'), + ); + } + } + } } diff --git a/backend/app/Repository/Interfaces/ProductCategoryRepositoryInterface.php b/backend/app/Repository/Interfaces/ProductCategoryRepositoryInterface.php new file mode 100644 index 0000000000..593fb5d7bf --- /dev/null +++ b/backend/app/Repository/Interfaces/ProductCategoryRepositoryInterface.php @@ -0,0 +1,18 @@ + + */ +interface ProductCategoryRepositoryInterface extends RepositoryInterface +{ + public function findByEventId(int $eventId, QueryParamsDTO $queryParamsDTO): Collection; + + public function getNextOrder(int $eventId); +} diff --git a/backend/app/Repository/Interfaces/ProductPriceRepositoryInterface.php b/backend/app/Repository/Interfaces/ProductPriceRepositoryInterface.php new file mode 100644 index 0000000000..23b538f1e4 --- /dev/null +++ b/backend/app/Repository/Interfaces/ProductPriceRepositoryInterface.php @@ -0,0 +1,15 @@ + + */ +interface ProductPriceRepositoryInterface extends RepositoryInterface +{ +} diff --git a/backend/app/Repository/Interfaces/ProductRepositoryInterface.php b/backend/app/Repository/Interfaces/ProductRepositoryInterface.php new file mode 100644 index 0000000000..d484fb0c89 --- /dev/null +++ b/backend/app/Repository/Interfaces/ProductRepositoryInterface.php @@ -0,0 +1,93 @@ + + */ +interface ProductRepositoryInterface extends RepositoryInterface +{ + /** + * @param int $eventId + * @param QueryParamsDTO $params + * @return LengthAwarePaginator + */ + public function findByEventId(int $eventId, QueryParamsDTO $params): LengthAwarePaginator; + + /** + * @param int $productId + * @param int $productPriceId + * @return int + */ + public function getQuantityRemainingForProductPrice(int $productId, int $productPriceId): int; + + /** + * @param int $productId + * @return Collection + */ + public function getTaxesByProductId(int $productId): Collection; + + /** + * @param int $taxId + * @return Collection + */ + public function getProductsByTaxId(int $taxId): Collection; + + /** + * @param int $productId + * @return Collection + */ + public function getCapacityAssignmentsByProductId(int $productId): Collection; + + /** + * @param int $productId + * @param array $taxIds + * @return void + */ + public function addTaxesAndFeesToProduct(int $productId, array $taxIds): void; + + /** + * @param array $productIds + * @param int $capacityAssignmentId + * @return void + */ + public function addCapacityAssignmentToProducts(int $capacityAssignmentId, array $productIds): void; + + /** + * @param int $checkInListId + * @param array $productIds + * @return void + */ + public function addCheckInListToProducts(int $checkInListId, array $productIds): void; + + /** + * @param int $checkInListId + * @return void + */ + public function removeCheckInListFromProducts(int $checkInListId): void; + + /** + * @param int $capacityAssignmentId + * @return void + */ + public function removeCapacityAssignmentFromProducts(int $capacityAssignmentId): void; + + + /** + * @param int $eventId + * @param array $productUpdates + * @param array $categoryUpdates + * @return void + */ + public function bulkUpdateProductsAndCategories(int $eventId, array $productUpdates, array $categoryUpdates): void; + + public function hasAssociatedOrders(int $productId): bool; +} diff --git a/backend/app/Repository/Interfaces/QuestionRepositoryInterface.php b/backend/app/Repository/Interfaces/QuestionRepositoryInterface.php index 6a5438de12..61c303e64e 100644 --- a/backend/app/Repository/Interfaces/QuestionRepositoryInterface.php +++ b/backend/app/Repository/Interfaces/QuestionRepositoryInterface.php @@ -13,9 +13,9 @@ interface QuestionRepositoryInterface extends RepositoryInterface { public function findByEventId(int $eventId): Collection; - public function create(array $attributes, array $ticketIds = []): QuestionDomainObject; + public function create(array $attributes, array $productIds = []): QuestionDomainObject; - public function updateQuestion(int $questionId, int $eventId, array $attributes, array $ticketIds = []): void; + public function updateQuestion(int $questionId, int $eventId, array $attributes, array $productIds = []): void; public function sortQuestions(int $eventId, array $orderedQuestionIds): void; } diff --git a/backend/app/Repository/Interfaces/RepositoryInterface.php b/backend/app/Repository/Interfaces/RepositoryInterface.php index 5481dbc8b8..dfa9c2c7bf 100644 --- a/backend/app/Repository/Interfaces/RepositoryInterface.php +++ b/backend/app/Repository/Interfaces/RepositoryInterface.php @@ -4,6 +4,7 @@ use Exception; use HiEvents\DomainObjects\Interfaces\DomainObjectInterface; +use HiEvents\Repository\Eloquent\Value\OrderAndDirection; use Illuminate\Contracts\Pagination\Paginator; use Illuminate\Database\Eloquent\Relations\Relation; use Illuminate\Pagination\LengthAwarePaginator; @@ -17,6 +18,9 @@ interface RepositoryInterface /** @var array */ public const DEFAULT_COLUMNS = ['*']; + /** @var string */ + public const DEFAULT_ORDER_DIRECTION = 'asc'; + /** @var int */ public const DEFAULT_PAGINATE_LIMIT = 20; @@ -100,9 +104,15 @@ public function findFirst(int $id, array $columns = self::DEFAULT_COLUMNS): ?Dom /** * @param array $where * @param array $columns + * @param OrderAndDirection[] $orderAndDirections * @return Collection */ - public function findWhere(array $where, array $columns = self::DEFAULT_COLUMNS): Collection; + public function findWhere( + array $where, + array $columns = self::DEFAULT_COLUMNS, + /** @var OrderAndDirection[] */ + array $orderAndDirections = [], + ): Collection; /** * @param array $where diff --git a/backend/app/Repository/Interfaces/TicketPriceRepositoryInterface.php b/backend/app/Repository/Interfaces/TicketPriceRepositoryInterface.php deleted file mode 100644 index 83df3a0bc7..0000000000 --- a/backend/app/Repository/Interfaces/TicketPriceRepositoryInterface.php +++ /dev/null @@ -1,15 +0,0 @@ - - */ -interface TicketPriceRepositoryInterface extends RepositoryInterface -{ -} diff --git a/backend/app/Repository/Interfaces/TicketRepositoryInterface.php b/backend/app/Repository/Interfaces/TicketRepositoryInterface.php deleted file mode 100644 index 4143956a51..0000000000 --- a/backend/app/Repository/Interfaces/TicketRepositoryInterface.php +++ /dev/null @@ -1,89 +0,0 @@ - - */ -interface TicketRepositoryInterface extends RepositoryInterface -{ - /** - * @param int $eventId - * @param QueryParamsDTO $params - * @return LengthAwarePaginator - */ - public function findByEventId(int $eventId, QueryParamsDTO $params): LengthAwarePaginator; - - /** - * @param int $ticketId - * @param int $ticketPriceId - * @return int - */ - public function getQuantityRemainingForTicketPrice(int $ticketId, int $ticketPriceId): int; - - /** - * @param int $ticketId - * @return Collection - */ - public function getTaxesByTicketId(int $ticketId): Collection; - - /** - * @param int $taxId - * @return Collection - */ - public function getTicketsByTaxId(int $taxId): Collection; - - /** - * @param int $ticketId - * @return Collection - */ - public function getCapacityAssignmentsByTicketId(int $ticketId): Collection; - - /** - * @param int $ticketId - * @param array $taxIds - * @return void - */ - public function addTaxesAndFeesToTicket(int $ticketId, array $taxIds): void; - - /** - * @param array $ticketIds - * @param int $capacityAssignmentId - * @return void - */ - public function addCapacityAssignmentToTickets(int $capacityAssignmentId, array $ticketIds): void; - - /** - * @param int $checkInListId - * @param array $ticketIds - * @return void - */ - public function addCheckInListToTickets(int $checkInListId, array $ticketIds): void; - - /** - * @param int $checkInListId - * @return void - */ - public function removeCheckInListFromTickets(int $checkInListId): void; - - /** - * @param int $capacityAssignmentId - * @return void - */ - public function removeCapacityAssignmentFromTickets(int $capacityAssignmentId): void; - - /** - * @param int $eventId - * @param array $orderedTicketIds - * @return void - */ - public function sortTickets(int $eventId, array $orderedTicketIds): void; -} diff --git a/backend/app/Resources/Attendee/AttendeeResource.php b/backend/app/Resources/Attendee/AttendeeResource.php index 77a7f924f3..a4b79c84a5 100644 --- a/backend/app/Resources/Attendee/AttendeeResource.php +++ b/backend/app/Resources/Attendee/AttendeeResource.php @@ -6,7 +6,7 @@ use HiEvents\DomainObjects\Enums\QuestionBelongsTo; use HiEvents\Resources\Order\OrderResource; use HiEvents\Resources\Question\QuestionAnswerViewResource; -use HiEvents\Resources\Ticket\TicketResource; +use HiEvents\Resources\Product\ProductResource; use Illuminate\Http\Request; use Illuminate\Http\Resources\Json\JsonResource; @@ -20,8 +20,8 @@ public function toArray(Request $request): array return [ 'id' => $this->getId(), 'order_id' => $this->getOrderId(), - 'ticket_id' => $this->getTicketId(), - 'ticket_price_id' => $this->getTicketPriceId(), + 'product_id' => $this->getProductId(), + 'product_price_id' => $this->getProductPriceId(), 'event_id' => $this->getEventId(), 'email' => $this->getEmail(), 'status' => $this->getStatus(), @@ -30,9 +30,9 @@ public function toArray(Request $request): array 'public_id' => $this->getPublicId(), 'short_id' => $this->getShortId(), 'locale' => $this->getLocale(), - 'ticket' => $this->when( - !is_null($this->getTicket()), - fn() => new TicketResource($this->getTicket()), + 'product' => $this->when( + !is_null($this->getProduct()), + fn() => new ProductResource($this->getProduct()), ), 'order' => $this->when( !is_null($this->getOrder()), @@ -42,7 +42,7 @@ public function toArray(Request $request): array $this->getQuestionAndAnswerViews() !== null, fn() => QuestionAnswerViewResource::collection( $this->getQuestionAndAnswerViews() - ?->filter(fn($qav) => $qav->getBelongsTo() === QuestionBelongsTo::TICKET->name) + ?->filter(fn($qav) => $qav->getBelongsTo() === QuestionBelongsTo::PRODUCT->name) ) ), diff --git a/backend/app/Resources/Attendee/AttendeeResourcePublic.php b/backend/app/Resources/Attendee/AttendeeResourcePublic.php index 8c40ffcd16..1f4ffc1a36 100644 --- a/backend/app/Resources/Attendee/AttendeeResourcePublic.php +++ b/backend/app/Resources/Attendee/AttendeeResourcePublic.php @@ -3,7 +3,7 @@ namespace HiEvents\Resources\Attendee; use HiEvents\DomainObjects\AttendeeDomainObject; -use HiEvents\Resources\Ticket\TicketMinimalResourcePublic; +use HiEvents\Resources\Product\ProductMinimalResourcePublic; use Illuminate\Http\Request; use Illuminate\Http\Resources\Json\JsonResource; @@ -22,9 +22,9 @@ public function toArray(Request $request): array 'last_name' => $this->getLastName(), 'public_id' => $this->getPublicId(), 'short_id' => $this->getShortId(), - 'ticket_id' => $this->getTicketId(), - 'ticket_price_id' => $this->getTicketPriceId(), - 'ticket' => $this->when((bool)$this->getTicket(), fn() => new TicketMinimalResourcePublic($this->getTicket())), + 'product_id' => $this->getProductId(), + 'product_price_id' => $this->getProductPriceId(), + 'product' => $this->when((bool)$this->getProduct(), fn() => new ProductMinimalResourcePublic($this->getProduct())), 'locale' => $this->getLocale(), ]; } diff --git a/backend/app/Resources/Attendee/AttendeeWithCheckInPublicResource.php b/backend/app/Resources/Attendee/AttendeeWithCheckInPublicResource.php index 9750f8c1f9..ee8b888e92 100644 --- a/backend/app/Resources/Attendee/AttendeeWithCheckInPublicResource.php +++ b/backend/app/Resources/Attendee/AttendeeWithCheckInPublicResource.php @@ -20,8 +20,8 @@ public function toArray(Request $request): array 'first_name' => $this->getFirstName(), 'last_name' => $this->getLastName(), 'public_id' => $this->getPublicId(), - 'ticket_id' => $this->getTicketId(), - 'ticket_price_id' => $this->getTicketPriceId(), + 'product_id' => $this->getProductId(), + 'product_price_id' => $this->getProductPriceId(), 'locale' => $this->getLocale(), $this->mergeWhen($this->getCheckIn() !== null, [ 'check_in' => new AttendeeCheckInPublicResource($this->getCheckIn()), diff --git a/backend/app/Resources/CapacityAssignment/CapacityAssignmentResource.php b/backend/app/Resources/CapacityAssignment/CapacityAssignmentResource.php index af6af46267..c35c6ef3b3 100644 --- a/backend/app/Resources/CapacityAssignment/CapacityAssignmentResource.php +++ b/backend/app/Resources/CapacityAssignment/CapacityAssignmentResource.php @@ -4,7 +4,7 @@ use HiEvents\DomainObjects\CapacityAssignmentDomainObject; use HiEvents\DomainObjects\Enums\CapacityAssignmentAppliesTo; -use HiEvents\DomainObjects\TicketDomainObject; +use HiEvents\DomainObjects\ProductDomainObject; use HiEvents\Resources\BaseResource; use Illuminate\Http\Request; @@ -25,11 +25,11 @@ public function toArray(Request $request): array 'status' => $this->getStatus(), 'event_id' => $this->getEventId(), $this->mergeWhen( - condition: $this->getTickets() !== null && $this->getAppliesTo() === CapacityAssignmentAppliesTo::TICKETS->name, + condition: $this->getProducts() !== null && $this->getAppliesTo() === CapacityAssignmentAppliesTo::PRODUCTS->name, value: [ - 'tickets' => $this->getTickets()?->map(fn(TicketDomainObject $ticket) => [ - 'id' => $ticket->getId(), - 'title' => $ticket->getTitle(), + 'products' => $this->getProducts()?->map(fn(ProductDomainObject $product) => [ + 'id' => $product->getId(), + 'title' => $product->getTitle(), ]), ]), ]; diff --git a/backend/app/Resources/CheckInList/CheckInListResource.php b/backend/app/Resources/CheckInList/CheckInListResource.php index 51d9690216..744f947c70 100644 --- a/backend/app/Resources/CheckInList/CheckInListResource.php +++ b/backend/app/Resources/CheckInList/CheckInListResource.php @@ -3,7 +3,7 @@ namespace HiEvents\Resources\CheckInList; use HiEvents\DomainObjects\CheckInListDomainObject; -use HiEvents\Resources\Ticket\TicketResource; +use HiEvents\Resources\Product\ProductResource; use Illuminate\Http\Resources\Json\JsonResource; /** @@ -26,8 +26,8 @@ public function toArray($request): array 'is_expired' => $this->isExpired($this->getEvent()->getTimezone()), 'is_active' => $this->isActivated($this->getEvent()->getTimezone()), ]), - $this->mergeWhen($this->getTickets() !== null, fn() => [ - 'tickets' => TicketResource::collection($this->getTickets()), + $this->mergeWhen($this->getProducts() !== null, fn() => [ + 'products' => ProductResource::collection($this->getProducts()), ]), ]; } diff --git a/backend/app/Resources/CheckInList/CheckInListResourcePublic.php b/backend/app/Resources/CheckInList/CheckInListResourcePublic.php index 0466870033..7135da87e4 100644 --- a/backend/app/Resources/CheckInList/CheckInListResourcePublic.php +++ b/backend/app/Resources/CheckInList/CheckInListResourcePublic.php @@ -4,7 +4,7 @@ use HiEvents\DomainObjects\CheckInListDomainObject; use HiEvents\Resources\Event\EventResourcePublic; -use HiEvents\Resources\Ticket\TicketMinimalResourcePublic; +use HiEvents\Resources\Product\ProductMinimalResourcePublic; use Illuminate\Http\Resources\Json\JsonResource; /** @@ -28,8 +28,8 @@ public function toArray($request): array 'is_active' => $this->isActivated($this->getEvent()->getTimezone()), 'event' => EventResourcePublic::make($this->getEvent()), ]), - $this->mergeWhen($this->getTickets() !== null, fn() => [ - 'tickets' => TicketMinimalResourcePublic::collection($this->getTickets()), + $this->mergeWhen($this->getProducts() !== null, fn() => [ + 'products' => ProductMinimalResourcePublic::collection($this->getProducts()), ]), ]; } diff --git a/backend/app/Resources/Event/EventResource.php b/backend/app/Resources/Event/EventResource.php index 7fcb425fd9..1a150524ff 100644 --- a/backend/app/Resources/Event/EventResource.php +++ b/backend/app/Resources/Event/EventResource.php @@ -6,7 +6,8 @@ use HiEvents\Resources\BaseResource; use HiEvents\Resources\Image\ImageResource; use HiEvents\Resources\Organizer\OrganizerResource; -use HiEvents\Resources\Ticket\TicketResource; +use HiEvents\Resources\Product\ProductResource; +use HiEvents\Resources\ProductCategory\ProductCategoryResource; use Illuminate\Http\Request; /** @@ -27,21 +28,28 @@ public function toArray(Request $request): array 'currency' => $this->getCurrency(), 'timezone' => $this->getTimezone(), 'slug' => $this->getSlug(), - 'tickets' => $this->when((bool)$this->getTickets(), fn() => TicketResource::collection($this->getTickets())), + 'products' => $this->when( + condition: (bool)$this->getProducts(), + value: fn() => ProductResource::collection($this->getProducts()), + ), + 'product_categories' => $this->when( + condition: (bool)$this->getProductCategories(), + value: fn() => ProductCategoryResource::collection($this->getProductCategories()), + ), 'attributes' => $this->when((bool)$this->getAttributes(), fn() => $this->getAttributes()), 'images' => $this->when((bool)$this->getImages(), fn() => ImageResource::collection($this->getImages())), 'location_details' => $this->when((bool)$this->getLocationDetails(), fn() => $this->getLocationDetails()), 'settings' => $this->when( - !is_null($this->getEventSettings()), - fn() => new EventSettingsResource($this->getEventSettings()) + condition: !is_null($this->getEventSettings()), + value: fn() => new EventSettingsResource($this->getEventSettings()) ), 'organizer' => $this->when( - !is_null($this->getOrganizer()), - fn() => new OrganizerResource($this->getOrganizer()) + condition: !is_null($this->getOrganizer()), + value: fn() => new OrganizerResource($this->getOrganizer()) ), 'statistics' => $this->when( - !is_null($this->getEventStatistics()), - fn() => new EventStatisticsResource($this->getEventStatistics()) + condition: !is_null($this->getEventStatistics()), + value: fn() => new EventStatisticsResource($this->getEventStatistics()) ), ]; } diff --git a/backend/app/Resources/Event/EventResourcePublic.php b/backend/app/Resources/Event/EventResourcePublic.php index d70dc0a103..a3ed701364 100644 --- a/backend/app/Resources/Event/EventResourcePublic.php +++ b/backend/app/Resources/Event/EventResourcePublic.php @@ -6,8 +6,8 @@ use HiEvents\Resources\BaseResource; use HiEvents\Resources\Image\ImageResource; use HiEvents\Resources\Organizer\OrganizerResourcePublic; +use HiEvents\Resources\ProductCategory\ProductCategoryResourcePublic; use HiEvents\Resources\Question\QuestionResource; -use HiEvents\Resources\Ticket\TicketResourcePublic; use Illuminate\Http\Request; /** @@ -38,30 +38,29 @@ public function toArray(Request $request): array 'lifecycle_status' => $this->getLifecycleStatus(), 'timezone' => $this->getTimezone(), 'location_details' => $this->when((bool)$this->getLocationDetails(), fn() => $this->getLocationDetails()), - - 'tickets' => $this->when( - !is_null($this->getTickets()), - fn() => TicketResourcePublic::collection($this->getTickets()) + 'product_categories' => $this->when( + condition: !is_null($this->getProductCategories()), + value: fn() => ProductCategoryResourcePublic::collection($this->getProductCategories()), ), 'settings' => $this->when( - !is_null($this->getEventSettings()), - fn() => new EventSettingsResourcePublic($this->getEventSettings(), $this->includePostCheckoutData), + condition: !is_null($this->getEventSettings()), + value: fn() => new EventSettingsResourcePublic($this->getEventSettings(), $this->includePostCheckoutData), ), // @TODO - public question resource 'questions' => $this->when( - !is_null($this->getQuestions()), - fn() => QuestionResource::collection($this->getQuestions()) + condition: !is_null($this->getQuestions()), + value: fn() => QuestionResource::collection($this->getQuestions()) ), 'attributes' => $this->when( - !is_null($this->getAttributes()), - fn() => collect($this->getAttributes())->reject(fn($attribute) => !$attribute['is_public'])), + condition: !is_null($this->getAttributes()), + value: fn() => collect($this->getAttributes())->reject(fn($attribute) => !$attribute['is_public'])), 'images' => $this->when( - !is_null($this->getImages()), - fn() => ImageResource::collection($this->getImages()) + condition: !is_null($this->getImages()), + value: fn() => ImageResource::collection($this->getImages()) ), 'organizer' => $this->when( - !is_null($this->getOrganizer()), - fn() => new OrganizerResourcePublic($this->getOrganizer()), + condition: !is_null($this->getOrganizer()), + value: fn() => new OrganizerResourcePublic($this->getOrganizer()), ), ]; } diff --git a/backend/app/Resources/Event/EventSettingsResource.php b/backend/app/Resources/Event/EventSettingsResource.php index 1886a27a22..b1a62b97c3 100644 --- a/backend/app/Resources/Event/EventSettingsResource.php +++ b/backend/app/Resources/Event/EventSettingsResource.php @@ -15,7 +15,7 @@ public function toArray($request): array return [ 'pre_checkout_message' => $this->getPreCheckoutMessage(), 'post_checkout_message' => $this->getPostCheckoutMessage(), - 'ticket_page_message' => $this->getTicketPageMessage(), + 'product_page_message' => $this->getProductPageMessage(), 'continue_button_text' => $this->getContinueButtonText(), 'required_attendee_details' => $this->getRequireAttendeeDetails(), 'email_footer_message' => $this->getEmailFooterMessage(), diff --git a/backend/app/Resources/Event/EventSettingsResourcePublic.php b/backend/app/Resources/Event/EventSettingsResourcePublic.php index 397a1d2a29..26bfca3fa8 100644 --- a/backend/app/Resources/Event/EventSettingsResourcePublic.php +++ b/backend/app/Resources/Event/EventSettingsResourcePublic.php @@ -28,7 +28,7 @@ public function toArray($request): array 'online_event_connection_details' => $this->getOnlineEventConnectionDetails(), ]), - 'ticket_page_message' => $this->getTicketPageMessage(), + 'product_page_message' => $this->getProductPageMessage(), 'continue_button_text' => $this->getContinueButtonText(), 'required_attendee_details' => $this->getRequireAttendeeDetails(), 'email_footer_message' => $this->getEmailFooterMessage(), diff --git a/backend/app/Resources/Event/EventStatisticsResource.php b/backend/app/Resources/Event/EventStatisticsResource.php index 91bd7d374f..1b9d5c14fe 100644 --- a/backend/app/Resources/Event/EventStatisticsResource.php +++ b/backend/app/Resources/Event/EventStatisticsResource.php @@ -20,7 +20,8 @@ public function toArray(Request $request): array 'total_tax' => $this->getTotalTax(), 'sales_total_before_additions' => $this->getSalesTotalBeforeAdditions(), 'total_fee' => $this->getTotalFee(), - 'tickets_sold' => $this->getTicketsSold(), + 'products_sold' => $this->getProductsSold(), + 'attendees_registered' => $this->getAttendeesRegistered(), 'total_refunded' => $this->getTotalRefunded(), ]; } diff --git a/backend/app/Resources/Message/MessageResource.php b/backend/app/Resources/Message/MessageResource.php index 75dc30d676..b4b5ce81ef 100644 --- a/backend/app/Resources/Message/MessageResource.php +++ b/backend/app/Resources/Message/MessageResource.php @@ -22,7 +22,7 @@ public function toArray(Request $request): array 'type' => $this->getType(), 'attendee_ids' => $this->getAttendeeIds(), 'order_id' => $this->getOrderId(), - 'ticket_ids' => $this->getTicketIds(), + 'product_ids' => $this->getProductIds(), 'sent_at' => $this->getCreatedAt(), 'status' => $this->getStatus(), 'message_preview' => $this->getMessagePreview(), diff --git a/backend/app/Resources/Order/OrderItemResource.php b/backend/app/Resources/Order/OrderItemResource.php index 18e4155943..3199ca498b 100644 --- a/backend/app/Resources/Order/OrderItemResource.php +++ b/backend/app/Resources/Order/OrderItemResource.php @@ -19,7 +19,7 @@ public function toArray(Request $request): array 'total_before_additions' => $this->getTotalBeforeAdditions(), 'price' => $this->getPrice(), 'quantity' => $this->getQuantity(), - 'ticket_id' => $this->getTicketId(), + 'product_id' => $this->getProductId(), 'item_name' => $this->getItemName(), 'price_before_discount' => $this->getPriceBeforeDiscount(), 'taxes_and_fees_rollup' => $this->getTaxesAndFeesRollup(), diff --git a/backend/app/Resources/Order/OrderItemResourcePublic.php b/backend/app/Resources/Order/OrderItemResourcePublic.php index 471f762f05..18ff026120 100644 --- a/backend/app/Resources/Order/OrderItemResourcePublic.php +++ b/backend/app/Resources/Order/OrderItemResourcePublic.php @@ -4,7 +4,7 @@ use HiEvents\DomainObjects\OrderItemDomainObject; use HiEvents\Resources\BaseResource; -use HiEvents\Resources\Ticket\TicketResourcePublic; +use HiEvents\Resources\Product\ProductResourcePublic; use Illuminate\Http\Request; /** @@ -22,14 +22,14 @@ public function toArray(Request $request): array 'price' => $this->getPrice(), 'price_before_discount' => $this->getPriceBeforeDiscount(), 'quantity' => $this->getQuantity(), - 'ticket_id' => $this->getTicketId(), - 'ticket_price_id' => $this->getTicketPriceId(), + 'product_id' => $this->getProductId(), + 'product_price_id' => $this->getProductPriceId(), 'item_name' => $this->getItemName(), 'total_service_fee' => $this->getTotalServiceFee(), 'total_tax' => $this->getTotalTax(), 'total_gross' => $this->getTotalGross(), 'taxes_and_fees_rollup' => $this->getTaxesAndFeesRollup(), - 'ticket' => $this->when((bool)$this->getTicket(), fn() => new TicketResourcePublic($this->getTicket())), + 'product' => $this->when((bool)$this->getProduct(), fn() => new ProductResourcePublic($this->getProduct())), ]; } } diff --git a/backend/app/Resources/Order/OrderResource.php b/backend/app/Resources/Order/OrderResource.php index b127d90db2..cec4399210 100644 --- a/backend/app/Resources/Order/OrderResource.php +++ b/backend/app/Resources/Order/OrderResource.php @@ -2,7 +2,6 @@ namespace HiEvents\Resources\Order; -use HiEvents\DomainObjects\Enums\QuestionBelongsTo; use HiEvents\DomainObjects\OrderDomainObject; use HiEvents\Resources\Attendee\AttendeeResource; use HiEvents\Resources\BaseResource; @@ -49,10 +48,7 @@ public function toArray(Request $request): array ), 'question_answers' => $this->when( !is_null($this->getQuestionAndAnswerViews()), - fn() => QuestionAnswerViewResource::collection( - $this->getQuestionAndAnswerViews() - ?->filter(fn($qav) => $qav->getBelongsTo() === QuestionBelongsTo::ORDER->name) - ) + fn() => QuestionAnswerViewResource::collection($this->getQuestionAndAnswerViews()), ), ]; } diff --git a/backend/app/Resources/Ticket/TicketMinimalResourcePublic.php b/backend/app/Resources/Product/ProductMinimalResourcePublic.php similarity index 51% rename from backend/app/Resources/Ticket/TicketMinimalResourcePublic.php rename to backend/app/Resources/Product/ProductMinimalResourcePublic.php index 10979bbd8b..daadaab8ba 100644 --- a/backend/app/Resources/Ticket/TicketMinimalResourcePublic.php +++ b/backend/app/Resources/Product/ProductMinimalResourcePublic.php @@ -1,15 +1,15 @@ $this->getType(), 'event_id' => $this->getEventId(), 'prices' => $this->when( - (bool)$this->getTicketPrices(), - fn() => TicketPriceResourcePublic::collection($this->getTicketPrices()), + (bool)$this->getProductPrices(), + fn() => ProductPriceResourcePublic::collection($this->getProductPrices()), ), + 'product_category_id' => $this->getProductCategoryId(), ]; } } diff --git a/backend/app/Resources/Ticket/TicketPriceResource.php b/backend/app/Resources/Product/ProductPriceResource.php similarity index 85% rename from backend/app/Resources/Ticket/TicketPriceResource.php rename to backend/app/Resources/Product/ProductPriceResource.php index 7a4844e394..3cbd945dc5 100644 --- a/backend/app/Resources/Ticket/TicketPriceResource.php +++ b/backend/app/Resources/Product/ProductPriceResource.php @@ -1,15 +1,15 @@ $this->getId(), 'title' => $this->getTitle(), 'type' => $this->getType(), + 'product_type' => $this->getProductType(), 'order' => $this->getOrder(), 'description' => $this->getDescription(), 'price' => $this->when( - $this->getType() !== TicketType::TIERED->name, + $this->getType() !== ProductPriceType::TIERED->name, fn() => $this->getPrice() ), - 'max_per_order' => $this->getMaxPerOrder() ?? self::DEFAULT_MAX_TICKETS, - 'min_per_order' => $this->getMinPerOrder() ?? self::DEFAULT_MIN_TICKETS, + 'max_per_order' => $this->getMaxPerOrder() ?? self::DEFAULT_MAX_PRODUCTS, + 'min_per_order' => $this->getMinPerOrder() ?? self::DEFAULT_MIN_PRODUCTS, 'quantity_sold' => $this->getQuantitySold(), 'sale_start_date' => $this->getSaleStartDate(), 'sale_end_date' => $this->getSaleEndDate(), @@ -46,7 +47,7 @@ public function toArray(Request $request): array 'is_before_sale_start_date' => $this->isBeforeSaleStartDate(), 'is_after_sale_end_date' => $this->isAfterSaleEndDate(), 'is_available' => $this->isAvailable(), - $this->mergeWhen((bool)$this->getTicketPrices(), fn() => [ + $this->mergeWhen((bool)$this->getProductPrices(), fn() => [ 'is_sold_out' => $this->isSoldOut(), ]), 'taxes_and_fees' => $this->when( @@ -54,9 +55,10 @@ public function toArray(Request $request): array fn() => TaxAndFeeResource::collection($this->getTaxAndFees()) ), 'prices' => $this->when( - (bool)$this->getTicketPrices(), - fn() => TicketPriceResource::collection($this->getTicketPrices()) + (bool)$this->getProductPrices(), + fn() => ProductPriceResource::collection($this->getProductPrices()) ), + 'product_category_id' => $this->getProductCategoryId(), ]; } } diff --git a/backend/app/Resources/Ticket/TicketResourcePublic.php b/backend/app/Resources/Product/ProductResourcePublic.php similarity index 70% rename from backend/app/Resources/Ticket/TicketResourcePublic.php rename to backend/app/Resources/Product/ProductResourcePublic.php index 788ed23cb4..1a25f6122b 100644 --- a/backend/app/Resources/Ticket/TicketResourcePublic.php +++ b/backend/app/Resources/Product/ProductResourcePublic.php @@ -1,16 +1,16 @@ $this->getQuantityAvailable(), ]), 'price' => $this->when( - $this->getTicketPrices() && !$this->isTieredType(), + $this->getProductPrices() && !$this->isTieredType(), fn() => $this->getPrice(), ), 'prices' => $this->when( - (bool)$this->getTicketPrices(), - fn() => TicketPriceResourcePublic::collectionWithAdditionalData($this->getTicketPrices(), [ - TicketPriceResourcePublic::SHOW_QUANTITY_AVAILABLE => $this->getShowQuantityRemaining(), + (bool)$this->getProductPrices(), + fn() => ProductPriceResourcePublic::collectionWithAdditionalData($this->getProductPrices(), [ + ProductPriceResourcePublic::SHOW_QUANTITY_AVAILABLE => $this->getShowQuantityRemaining(), ]), ), 'taxes' => $this->when( (bool)$this->getTaxAndFees(), fn() => TaxAndFeeResource::collection($this->getTaxAndFees()) ), - $this->mergeWhen((bool)$this->getTicketPrices(), fn() => [ + $this->mergeWhen((bool)$this->getProductPrices(), fn() => [ 'is_available' => $this->isAvailable(), 'is_sold_out' => $this->isSoldOut(), ]), + 'product_category_id' => $this->getProductCategoryId(), ]; } } diff --git a/backend/app/Resources/ProductCategory/ProductCategoryResource.php b/backend/app/Resources/ProductCategory/ProductCategoryResource.php new file mode 100644 index 0000000000..1d909545b2 --- /dev/null +++ b/backend/app/Resources/ProductCategory/ProductCategoryResource.php @@ -0,0 +1,28 @@ + $this->getId(), + 'name' => $this->getName(), + 'description' => $this->getDescription(), + 'is_hidden' => $this->getIsHidden(), + 'order' => $this->getOrder(), + 'no_products_message' => $this->getNoProductsMessage(), + $this->mergeWhen((bool)$this->getProducts(), fn() => [ + 'products' => ProductResource::collection($this->getProducts()), + ]), + ]; + } +} diff --git a/backend/app/Resources/ProductCategory/ProductCategoryResourcePublic.php b/backend/app/Resources/ProductCategory/ProductCategoryResourcePublic.php new file mode 100644 index 0000000000..dd885fb80e --- /dev/null +++ b/backend/app/Resources/ProductCategory/ProductCategoryResourcePublic.php @@ -0,0 +1,28 @@ + $this->getId(), + 'name' => $this->getName(), + 'description' => $this->getDescription(), + 'is_hidden' => $this->getIsHidden(), + 'order' => $this->getOrder(), + 'no_products_message' => $this->getNoProductsMessage(), + $this->mergeWhen((bool)$this->getProducts(), fn() => [ + 'products' => ProductResource::collection($this->getProducts()), + ]), + ]; + } +} diff --git a/backend/app/Resources/PromoCode/PromoCodeResource.php b/backend/app/Resources/PromoCode/PromoCodeResource.php index 0aa9d80a54..ec713886ad 100644 --- a/backend/app/Resources/PromoCode/PromoCodeResource.php +++ b/backend/app/Resources/PromoCode/PromoCodeResource.php @@ -16,7 +16,7 @@ public function toArray(Request $request): array return [ 'id' => $this->getId(), 'code' => $this->getCode(), - 'applicable_ticket_ids' => $this->getApplicableTicketIds(), + 'applicable_product_ids' => $this->getApplicableProductIds(), 'discount' => $this->getDiscount(), 'discount_type' => $this->getDiscountType(), 'created_at' => $this->getCreatedAt(), diff --git a/backend/app/Resources/Question/QuestionAnswerViewResource.php b/backend/app/Resources/Question/QuestionAnswerViewResource.php index 6b58307d20..7c3fae4fee 100644 --- a/backend/app/Resources/Question/QuestionAnswerViewResource.php +++ b/backend/app/Resources/Question/QuestionAnswerViewResource.php @@ -16,6 +16,8 @@ class QuestionAnswerViewResource extends JsonResource public function toArray(Request $request): array { return [ + 'product_id' => $this->getProductId(), + 'product_title' => $this->getProductTitle(), 'question_id' => $this->getQuestionId(), 'title' => $this->getTitle(), 'answer' => $this->getAnswer(), diff --git a/backend/app/Resources/Question/QuestionResource.php b/backend/app/Resources/Question/QuestionResource.php index 7f0591dde2..96bdf29651 100644 --- a/backend/app/Resources/Question/QuestionResource.php +++ b/backend/app/Resources/Question/QuestionResource.php @@ -23,9 +23,9 @@ public function toArray(Request $request): array 'event_id' => $this->getEventId(), 'belongs_to' => $this->getBelongsTo(), 'is_hidden' => $this->getIsHidden(), - 'ticket_ids' => $this->when( - !is_null($this->getTickets()), - fn() => $this->getTickets()->map(fn($ticket) => $ticket->getId()) + 'product_ids' => $this->when( + !is_null($this->getProducts()), + fn() => $this->getProducts()->map(fn($product) => $product->getId()) ), ]; } diff --git a/backend/app/Resources/Question/QuestionResourcePublic.php b/backend/app/Resources/Question/QuestionResourcePublic.php index 5820cb8ba8..d4e1288de4 100644 --- a/backend/app/Resources/Question/QuestionResourcePublic.php +++ b/backend/app/Resources/Question/QuestionResourcePublic.php @@ -22,9 +22,9 @@ public function toArray(Request $request): array 'required' => $this->getRequired(), 'event_id' => $this->getEventId(), 'belongs_to' => $this->getBelongsTo(), - 'ticket_ids' => $this->when( - !is_null($this->getTickets()), - fn() => $this->getTickets()->map(fn($ticket) => $ticket->getId()) + 'product_ids' => $this->when( + !is_null($this->getProducts()), + fn() => $this->getProducts()->map(fn($product) => $product->getId()) ), ]; } diff --git a/backend/app/Services/Domain/Attendee/SendAttendeeTicketService.php b/backend/app/Services/Domain/Attendee/SendAttendeeTicketService.php index 27da9f08ae..7b45c5b903 100644 --- a/backend/app/Services/Domain/Attendee/SendAttendeeTicketService.php +++ b/backend/app/Services/Domain/Attendee/SendAttendeeTicketService.php @@ -9,10 +9,10 @@ use HiEvents\Mail\Attendee\AttendeeTicketMail; use Illuminate\Contracts\Mail\Mailer; -readonly class SendAttendeeTicketService +class SendAttendeeTicketService { public function __construct( - private Mailer $mailer + private readonly Mailer $mailer ) { } diff --git a/backend/app/Services/Domain/CapacityAssignment/CapacityAssignmentTicketAssociationService.php b/backend/app/Services/Domain/CapacityAssignment/CapacityAssignmentProductAssociationService.php similarity index 50% rename from backend/app/Services/Domain/CapacityAssignment/CapacityAssignmentTicketAssociationService.php rename to backend/app/Services/Domain/CapacityAssignment/CapacityAssignmentProductAssociationService.php index c5a0f5a2f8..e7f1b68de7 100644 --- a/backend/app/Services/Domain/CapacityAssignment/CapacityAssignmentTicketAssociationService.php +++ b/backend/app/Services/Domain/CapacityAssignment/CapacityAssignmentProductAssociationService.php @@ -2,52 +2,52 @@ namespace HiEvents\Services\Domain\CapacityAssignment; -use HiEvents\Repository\Interfaces\TicketRepositoryInterface; +use HiEvents\Repository\Interfaces\ProductRepositoryInterface; use Illuminate\Database\DatabaseManager; -class CapacityAssignmentTicketAssociationService +class CapacityAssignmentProductAssociationService { public function __construct( - private readonly TicketRepositoryInterface $ticketRepository, - public readonly DatabaseManager $databaseManager, + private readonly ProductRepositoryInterface $productRepository, + public readonly DatabaseManager $databaseManager, ) { } - public function addCapacityToTickets( + public function addCapacityToProducts( int $capacityAssignmentId, - ?array $ticketIds, + ?array $productIds, bool $removePreviousAssignments = true ): void { - $this->databaseManager->transaction(function () use ($capacityAssignmentId, $ticketIds, $removePreviousAssignments) { - $this->associateTicketsWithCapacityAssignment( + $this->databaseManager->transaction(function () use ($capacityAssignmentId, $productIds, $removePreviousAssignments) { + $this->associateProductsWithCapacityAssignment( capacityAssignmentId: $capacityAssignmentId, - ticketIds: $ticketIds, + productIds: $productIds, removePreviousAssignments: $removePreviousAssignments, ); }); } - private function associateTicketsWithCapacityAssignment( + private function associateProductsWithCapacityAssignment( int $capacityAssignmentId, - ?array $ticketIds, + ?array $productIds, bool $removePreviousAssignments = true ): void { - if (empty($ticketIds)) { + if (empty($productIds)) { return; } if ($removePreviousAssignments) { - $this->ticketRepository->removeCapacityAssignmentFromTickets( + $this->productRepository->removeCapacityAssignmentFromProducts( capacityAssignmentId: $capacityAssignmentId, ); } - $this->ticketRepository->addCapacityAssignmentToTickets( + $this->productRepository->addCapacityAssignmentToProducts( capacityAssignmentId: $capacityAssignmentId, - ticketIds: array_unique($ticketIds), + productIds: array_unique($productIds), ); } } diff --git a/backend/app/Services/Domain/CapacityAssignment/CreateCapacityAssignmentService.php b/backend/app/Services/Domain/CapacityAssignment/CreateCapacityAssignmentService.php index b5bb21a88f..4c7e68c6e6 100644 --- a/backend/app/Services/Domain/CapacityAssignment/CreateCapacityAssignmentService.php +++ b/backend/app/Services/Domain/CapacityAssignment/CreateCapacityAssignmentService.php @@ -5,11 +5,11 @@ use HiEvents\DomainObjects\CapacityAssignmentDomainObject; use HiEvents\DomainObjects\Enums\CapacityAssignmentAppliesTo; use HiEvents\DomainObjects\Generated\CapacityAssignmentDomainObjectAbstract; -use HiEvents\DomainObjects\TicketPriceDomainObject; +use HiEvents\DomainObjects\ProductPriceDomainObject; use HiEvents\Repository\Interfaces\CapacityAssignmentRepositoryInterface; -use HiEvents\Repository\Interfaces\TicketPriceRepositoryInterface; -use HiEvents\Services\Domain\Ticket\EventTicketValidationService; -use HiEvents\Services\Domain\Ticket\Exception\UnrecognizedTicketIdException; +use HiEvents\Repository\Interfaces\ProductPriceRepositoryInterface; +use HiEvents\Services\Domain\Product\EventProductValidationService; +use HiEvents\Services\Domain\Product\Exception\UnrecognizedProductIdException; use Illuminate\Database\DatabaseManager; class CreateCapacityAssignmentService @@ -17,32 +17,32 @@ class CreateCapacityAssignmentService public function __construct( private readonly DatabaseManager $databaseManager, private readonly CapacityAssignmentRepositoryInterface $capacityAssignmentRepository, - private readonly EventTicketValidationService $eventTicketValidationService, - private readonly CapacityAssignmentTicketAssociationService $capacityAssignmentTicketAssociationService, - private readonly TicketPriceRepositoryInterface $ticketPriceRepository, + private readonly EventProductValidationService $eventProductValidationService, + private readonly CapacityAssignmentProductAssociationService $capacityAssignmentProductAssociationService, + private readonly ProductPriceRepositoryInterface $productPriceRepository, ) { } /** - * @throws UnrecognizedTicketIdException + * @throws UnrecognizedProductIdException */ public function createCapacityAssignment( CapacityAssignmentDomainObject $capacityAssignment, - array $ticketIds, + array $productIds, ): CapacityAssignmentDomainObject { - $this->eventTicketValidationService->validateTicketIds($ticketIds, $capacityAssignment->getEventId()); + $this->eventProductValidationService->validateProductIds($productIds, $capacityAssignment->getEventId()); - return $this->persistAssignmentAndAssociateTickets($capacityAssignment, $ticketIds); + return $this->persistAssignmentAndAssociateProducts($capacityAssignment, $productIds); } - private function persistAssignmentAndAssociateTickets( + private function persistAssignmentAndAssociateProducts( CapacityAssignmentDomainObject $capacityAssignment, - ?array $ticketIds, + ?array $productIds, ): CapacityAssignmentDomainObject { - return $this->databaseManager->transaction(function () use ($capacityAssignment, $ticketIds) { + return $this->databaseManager->transaction(function () use ($capacityAssignment, $productIds) { /** @var CapacityAssignmentDomainObject $capacityAssignment */ $capacityAssignment = $this->capacityAssignmentRepository->create([ CapacityAssignmentDomainObjectAbstract::NAME => $capacityAssignment->getName(), @@ -50,13 +50,13 @@ private function persistAssignmentAndAssociateTickets( CapacityAssignmentDomainObjectAbstract::CAPACITY => $capacityAssignment->getCapacity(), CapacityAssignmentDomainObjectAbstract::APPLIES_TO => $capacityAssignment->getAppliesTo(), CapacityAssignmentDomainObjectAbstract::STATUS => $capacityAssignment->getStatus(), - CapacityAssignmentDomainObjectAbstract::USED_CAPACITY => $this->getUsedCapacity($ticketIds), + CapacityAssignmentDomainObjectAbstract::USED_CAPACITY => $this->getUsedCapacity($productIds), ]); - if ($capacityAssignment->getAppliesTo() === CapacityAssignmentAppliesTo::TICKETS->name) { - $this->capacityAssignmentTicketAssociationService->addCapacityToTickets( + if ($capacityAssignment->getAppliesTo() === CapacityAssignmentAppliesTo::PRODUCTS->name) { + $this->capacityAssignmentProductAssociationService->addCapacityToProducts( capacityAssignmentId: $capacityAssignment->getId(), - ticketIds: $ticketIds, + productIds: $productIds, removePreviousAssignments: false, ); } @@ -65,10 +65,10 @@ private function persistAssignmentAndAssociateTickets( }); } - private function getUsedCapacity(array $ticketIds): int + private function getUsedCapacity(array $productIds): int { - $ticketPrices = $this->ticketPriceRepository->findWhereIn('ticket_id', $ticketIds); + $productPrices = $this->productPriceRepository->findWhereIn('product_id', $productIds); - return $ticketPrices->sum(fn(TicketPriceDomainObject $ticketPrice) => $ticketPrice->getQuantitySold()); + return $productPrices->sum(fn(ProductPriceDomainObject $productPrice) => $productPrice->getQuantitySold()); } } diff --git a/backend/app/Services/Domain/CapacityAssignment/Exception/TicketsDoNotBelongToEventException.php b/backend/app/Services/Domain/CapacityAssignment/Exception/TicketsDoNotBelongToEventException.php index 8e2cbefb8a..1505af45b5 100644 --- a/backend/app/Services/Domain/CapacityAssignment/Exception/TicketsDoNotBelongToEventException.php +++ b/backend/app/Services/Domain/CapacityAssignment/Exception/TicketsDoNotBelongToEventException.php @@ -4,7 +4,7 @@ use Exception; -class TicketsDoNotBelongToEventException extends Exception +class ProductsDoNotBelongToEventException extends Exception { } diff --git a/backend/app/Services/Domain/CapacityAssignment/UpdateCapacityAssignmentService.php b/backend/app/Services/Domain/CapacityAssignment/UpdateCapacityAssignmentService.php index a0d0899a27..b355cd00ac 100644 --- a/backend/app/Services/Domain/CapacityAssignment/UpdateCapacityAssignmentService.php +++ b/backend/app/Services/Domain/CapacityAssignment/UpdateCapacityAssignmentService.php @@ -6,8 +6,8 @@ use HiEvents\DomainObjects\Enums\CapacityAssignmentAppliesTo; use HiEvents\DomainObjects\Generated\CapacityAssignmentDomainObjectAbstract; use HiEvents\Repository\Interfaces\CapacityAssignmentRepositoryInterface; -use HiEvents\Services\Domain\Ticket\EventTicketValidationService; -use HiEvents\Services\Domain\Ticket\Exception\UnrecognizedTicketIdException; +use HiEvents\Services\Domain\Product\EventProductValidationService; +use HiEvents\Services\Domain\Product\Exception\UnrecognizedProductIdException; use Illuminate\Database\DatabaseManager; class UpdateCapacityAssignmentService @@ -15,33 +15,33 @@ class UpdateCapacityAssignmentService public function __construct( private readonly DatabaseManager $databaseManager, private readonly CapacityAssignmentRepositoryInterface $capacityAssignmentRepository, - private readonly EventTicketValidationService $eventTicketValidationService, - private readonly CapacityAssignmentTicketAssociationService $capacityAssignmentTicketAssociationService, + private readonly EventProductValidationService $eventProductValidationService, + private readonly CapacityAssignmentProductAssociationService $capacityAssignmentProductAssociationService, ) { } /** - * @throws UnrecognizedTicketIdException + * @throws UnrecognizedProductIdException */ public function updateCapacityAssignment( CapacityAssignmentDomainObject $capacityAssignment, - ?array $ticketIds = null, + ?array $productIds = null, ): CapacityAssignmentDomainObject { - if ($ticketIds !== null) { - $this->eventTicketValidationService->validateTicketIds($ticketIds, $capacityAssignment->getEventId()); + if ($productIds !== null) { + $this->eventProductValidationService->validateProductIds($productIds, $capacityAssignment->getEventId()); } - return $this->updateAssignmentAndAssociateTickets($capacityAssignment, $ticketIds); + return $this->updateAssignmentAndAssociateProducts($capacityAssignment, $productIds); } - private function updateAssignmentAndAssociateTickets( + private function updateAssignmentAndAssociateProducts( CapacityAssignmentDomainObject $capacityAssignment, - ?array $ticketIds + ?array $productIds ): CapacityAssignmentDomainObject { - return $this->databaseManager->transaction(function () use ($capacityAssignment, $ticketIds) { + return $this->databaseManager->transaction(function () use ($capacityAssignment, $productIds) { /** @var CapacityAssignmentDomainObject $capacityAssignment */ $this->capacityAssignmentRepository->updateWhere( attributes: [ @@ -57,10 +57,10 @@ private function updateAssignmentAndAssociateTickets( ] ); - if ($capacityAssignment->getAppliesTo() === CapacityAssignmentAppliesTo::TICKETS->name) { - $this->capacityAssignmentTicketAssociationService->addCapacityToTickets( + if ($capacityAssignment->getAppliesTo() === CapacityAssignmentAppliesTo::PRODUCTS->name) { + $this->capacityAssignmentProductAssociationService->addCapacityToProducts( capacityAssignmentId: $capacityAssignment->getId(), - ticketIds: $ticketIds, + productIds: $productIds, ); } diff --git a/backend/app/Services/Domain/CheckInList/CheckInListDataService.php b/backend/app/Services/Domain/CheckInList/CheckInListDataService.php index 3795ad043a..f80a8c3c7b 100644 --- a/backend/app/Services/Domain/CheckInList/CheckInListDataService.php +++ b/backend/app/Services/Domain/CheckInList/CheckInListDataService.php @@ -7,7 +7,7 @@ use HiEvents\DomainObjects\CheckInListDomainObject; use HiEvents\DomainObjects\Generated\AttendeeDomainObjectAbstract; use HiEvents\DomainObjects\Generated\CheckInListDomainObjectAbstract; -use HiEvents\DomainObjects\TicketDomainObject; +use HiEvents\DomainObjects\ProductDomainObject; use HiEvents\Exceptions\CannotCheckInException; use HiEvents\Repository\Interfaces\AttendeeRepositoryInterface; use HiEvents\Repository\Interfaces\CheckInListRepositoryInterface; @@ -30,9 +30,9 @@ public function verifyAttendeeBelongsToCheckInList( AttendeeDomainObject $attendee, ): void { - $allowedTicketIds = $checkInList->getTickets()->map(fn($ticket) => $ticket->getId())->toArray() ?? []; + $allowedProductIds = $checkInList->getProducts()->map(fn($product) => $product->getId())->toArray() ?? []; - if (!in_array($attendee->getTicketId(), $allowedTicketIds, true)) { + if (!in_array($attendee->getProductId(), $allowedProductIds, true)) { throw new CannotCheckInException( __('Attendee :attendee_name is not allowed to check in using this check-in list', [ 'attendee_name' => $attendee->getFullName(), @@ -74,7 +74,7 @@ public function getAttendees(array $attendeePublicIds): Collection public function getCheckInList(string $checkInListUuid): CheckInListDomainObject { $checkInList = $this->checkInListRepository - ->loadRelation(TicketDomainObject::class) + ->loadRelation(ProductDomainObject::class) ->findFirstWhere([ CheckInListDomainObjectAbstract::SHORT_ID => $checkInListUuid, ]); diff --git a/backend/app/Services/Domain/CheckInList/CheckInListTicketAssociationService.php b/backend/app/Services/Domain/CheckInList/CheckInListProductAssociationService.php similarity index 50% rename from backend/app/Services/Domain/CheckInList/CheckInListTicketAssociationService.php rename to backend/app/Services/Domain/CheckInList/CheckInListProductAssociationService.php index 24c96c4e16..2153c44620 100644 --- a/backend/app/Services/Domain/CheckInList/CheckInListTicketAssociationService.php +++ b/backend/app/Services/Domain/CheckInList/CheckInListProductAssociationService.php @@ -2,52 +2,52 @@ namespace HiEvents\Services\Domain\CheckInList; -use HiEvents\Repository\Interfaces\TicketRepositoryInterface; +use HiEvents\Repository\Interfaces\ProductRepositoryInterface; use Illuminate\Database\DatabaseManager; -class CheckInListTicketAssociationService +class CheckInListProductAssociationService { public function __construct( - private readonly TicketRepositoryInterface $ticketRepository, - public readonly DatabaseManager $databaseManager, + private readonly ProductRepositoryInterface $productRepository, + public readonly DatabaseManager $databaseManager, ) { } - public function addCheckInListToTickets( + public function addCheckInListToProducts( int $checkInListId, - ?array $ticketIds, + ?array $productIds, bool $removePreviousAssignments = true ): void { - $this->databaseManager->transaction(function () use ($checkInListId, $ticketIds, $removePreviousAssignments) { - $this->associateTicketsWithCheckInList( + $this->databaseManager->transaction(function () use ($checkInListId, $productIds, $removePreviousAssignments) { + $this->associateProductsWithCheckInList( checkInListId: $checkInListId, - ticketIds: $ticketIds, + productIds: $productIds, removePreviousAssignments: $removePreviousAssignments, ); }); } - private function associateTicketsWithCheckInList( + private function associateProductsWithCheckInList( int $checkInListId, - ?array $ticketIds, + ?array $productIds, bool $removePreviousAssignments = true ): void { - if (empty($ticketIds)) { + if (empty($productIds)) { return; } if ($removePreviousAssignments) { - $this->ticketRepository->removeCheckInListFromTickets( + $this->productRepository->removeCheckInListFromProducts( checkInListId: $checkInListId, ); } - $this->ticketRepository->addCheckInListToTickets( + $this->productRepository->addCheckInListToProducts( checkInListId: $checkInListId, - ticketIds: array_unique($ticketIds), + productIds: array_unique($productIds), ); } } diff --git a/backend/app/Services/Domain/CheckInList/CreateAttendeeCheckInService.php b/backend/app/Services/Domain/CheckInList/CreateAttendeeCheckInService.php index 737619b5ba..127ab15767 100644 --- a/backend/app/Services/Domain/CheckInList/CreateAttendeeCheckInService.php +++ b/backend/app/Services/Domain/CheckInList/CreateAttendeeCheckInService.php @@ -64,7 +64,7 @@ public function checkInAttendees( if ($attendee->getStatus() === AttendeeStatus::CANCELLED->name) { $errors->addError( key: $attendee->getPublicId(), - message: __('Attendee :attendee_name\'s ticket is cancelled', [ + message: __('Attendee :attendee_name\'s product is cancelled', [ 'attendee_name' => $attendee->getFullName(), ]) ); @@ -87,7 +87,7 @@ public function checkInAttendees( AttendeeCheckInDomainObjectAbstract::ATTENDEE_ID => $attendee->getId(), AttendeeCheckInDomainObjectAbstract::CHECK_IN_LIST_ID => $checkInList->getId(), AttendeeCheckInDomainObjectAbstract::IP_ADDRESS => $checkInUserIpAddress, - AttendeeCheckInDomainObjectAbstract::TICKET_ID => $attendee->getTicketId(), + AttendeeCheckInDomainObjectAbstract::PRODUCT_ID => $attendee->getProductId(), AttendeeCheckInDomainObjectAbstract::SHORT_ID => IdHelper::shortId(IdHelper::CHECK_IN_PREFIX), AttendeeCheckInDomainObjectAbstract::EVENT_ID => $checkInList->getEventId(), ]) diff --git a/backend/app/Services/Domain/CheckInList/CreateCheckInListService.php b/backend/app/Services/Domain/CheckInList/CreateCheckInListService.php index 93aa2938de..a4e95f929c 100644 --- a/backend/app/Services/Domain/CheckInList/CreateCheckInListService.php +++ b/backend/app/Services/Domain/CheckInList/CreateCheckInListService.php @@ -8,16 +8,16 @@ use HiEvents\Helper\IdHelper; use HiEvents\Repository\Interfaces\CheckInListRepositoryInterface; use HiEvents\Repository\Interfaces\EventRepositoryInterface; -use HiEvents\Services\Domain\Ticket\EventTicketValidationService; -use HiEvents\Services\Domain\Ticket\Exception\UnrecognizedTicketIdException; +use HiEvents\Services\Domain\Product\EventProductValidationService; +use HiEvents\Services\Domain\Product\Exception\UnrecognizedProductIdException; use Illuminate\Database\DatabaseManager; class CreateCheckInListService { public function __construct( private readonly CheckInListRepositoryInterface $checkInListRepository, - private readonly EventTicketValidationService $eventTicketValidationService, - private readonly CheckInListTicketAssociationService $checkInListTicketAssociationService, + private readonly EventProductValidationService $eventProductValidationService, + private readonly CheckInListProductAssociationService $checkInListProductAssociationService, private readonly DatabaseManager $databaseManager, private readonly EventRepositoryInterface $eventRepository, @@ -26,12 +26,12 @@ public function __construct( } /** - * @throws UnrecognizedTicketIdException + * @throws UnrecognizedProductIdException */ - public function createCheckInList(CheckInListDomainObject $checkInList, array $ticketIds): CheckInListDomainObject + public function createCheckInList(CheckInListDomainObject $checkInList, array $productIds): CheckInListDomainObject { - return $this->databaseManager->transaction(function () use ($checkInList, $ticketIds) { - $this->eventTicketValidationService->validateTicketIds($ticketIds, $checkInList->getEventId()); + return $this->databaseManager->transaction(function () use ($checkInList, $productIds) { + $this->eventProductValidationService->validateProductIds($productIds, $checkInList->getEventId()); $event = $this->eventRepository->findById($checkInList->getEventId()); $newCheckInList = $this->checkInListRepository->create([ @@ -47,9 +47,9 @@ public function createCheckInList(CheckInListDomainObject $checkInList, array $t CheckInListDomainObjectAbstract::SHORT_ID => IdHelper::shortId(IdHelper::CHECK_IN_LIST_PREFIX), ]); - $this->checkInListTicketAssociationService->addCheckInListToTickets( + $this->checkInListProductAssociationService->addCheckInListToProducts( checkInListId: $newCheckInList->getId(), - ticketIds: $ticketIds, + productIds: $productIds, removePreviousAssignments: false, ); diff --git a/backend/app/Services/Domain/CheckInList/UpdateCheckInListService.php b/backend/app/Services/Domain/CheckInList/UpdateCheckInListService.php index b26fd8b6c1..11a441deaf 100644 --- a/backend/app/Services/Domain/CheckInList/UpdateCheckInListService.php +++ b/backend/app/Services/Domain/CheckInList/UpdateCheckInListService.php @@ -7,16 +7,16 @@ use HiEvents\Helper\DateHelper; use HiEvents\Repository\Interfaces\CheckInListRepositoryInterface; use HiEvents\Repository\Interfaces\EventRepositoryInterface; -use HiEvents\Services\Domain\Ticket\EventTicketValidationService; -use HiEvents\Services\Domain\Ticket\Exception\UnrecognizedTicketIdException; +use HiEvents\Services\Domain\Product\EventProductValidationService; +use HiEvents\Services\Domain\Product\Exception\UnrecognizedProductIdException; use Illuminate\Database\DatabaseManager; class UpdateCheckInListService { public function __construct( private readonly DatabaseManager $databaseManager, - private readonly EventTicketValidationService $eventTicketValidationService, - private readonly CheckInListTicketAssociationService $checkInListTicketAssociationService, + private readonly EventProductValidationService $eventProductValidationService, + private readonly CheckInListProductAssociationService $checkInListProductAssociationService, private readonly CheckInListRepositoryInterface $checkInListRepository, private readonly EventRepositoryInterface $eventRepository, ) @@ -24,12 +24,12 @@ public function __construct( } /** - * @throws UnrecognizedTicketIdException + * @throws UnrecognizedProductIdException */ - public function updateCheckInList(CheckInListDomainObject $checkInList, array $ticketIds): CheckInListDomainObject + public function updateCheckInList(CheckInListDomainObject $checkInList, array $productIds): CheckInListDomainObject { - return $this->databaseManager->transaction(function () use ($checkInList, $ticketIds) { - $this->eventTicketValidationService->validateTicketIds($ticketIds, $checkInList->getEventId()); + return $this->databaseManager->transaction(function () use ($checkInList, $productIds) { + $this->eventProductValidationService->validateProductIds($productIds, $checkInList->getEventId()); $event = $this->eventRepository->findById($checkInList->getEventId()); $this->checkInListRepository->updateWhere( @@ -50,9 +50,9 @@ public function updateCheckInList(CheckInListDomainObject $checkInList, array $t ] ); - $this->checkInListTicketAssociationService->addCheckInListToTickets( + $this->checkInListProductAssociationService->addCheckInListToProducts( checkInListId: $checkInList->getId(), - ticketIds: $ticketIds, + productIds: $productIds, ); return $this->checkInListRepository->findFirstWhere( diff --git a/backend/app/Services/Domain/Event/CreateEventImageService.php b/backend/app/Services/Domain/Event/CreateEventImageService.php index a2b6eb73ba..7c9f855742 100644 --- a/backend/app/Services/Domain/Event/CreateEventImageService.php +++ b/backend/app/Services/Domain/Event/CreateEventImageService.php @@ -43,7 +43,7 @@ public function createImage( image: $image, entityId: $eventId, entityType: EventDomainObject::class, - imageType: EventImageType::EVENT_COVER->name, + imageType: $type->name, ); }); } diff --git a/backend/app/Services/Domain/Event/CreateEventService.php b/backend/app/Services/Domain/Event/CreateEventService.php index 4d8fdd8648..98e6c6a8d9 100644 --- a/backend/app/Services/Domain/Event/CreateEventService.php +++ b/backend/app/Services/Domain/Event/CreateEventService.php @@ -13,6 +13,7 @@ use HiEvents\Repository\Interfaces\EventSettingsRepositoryInterface; use HiEvents\Repository\Interfaces\EventStatisticRepositoryInterface; use HiEvents\Repository\Interfaces\OrganizerRepositoryInterface; +use HiEvents\Services\Domain\ProductCategory\CreateProductCategoryService; use HTMLPurifier; use Illuminate\Database\DatabaseManager; use Throwable; @@ -26,6 +27,7 @@ public function __construct( private readonly DatabaseManager $databaseManager, private readonly EventStatisticRepositoryInterface $eventStatisticsRepository, private readonly HTMLPurifier $purifier, + private readonly CreateProductCategoryService $createProductCategoryService, ) { } @@ -55,6 +57,8 @@ public function createEvent( $this->createEventStatistics($event); + $this->createDefaultProductCategory($event); + $this->databaseManager->commit(); return $event; @@ -104,7 +108,7 @@ private function createEventStatistics(EventDomainObject $event): void { $this->eventStatisticsRepository->create([ 'event_id' => $event->getId(), - 'tickets_sold' => 0, + 'products_sold' => 0, 'sales_total_gross' => 0, 'sales_total_before_additions' => 0, 'total_tax' => 0, @@ -143,4 +147,15 @@ private function createEventSettings( 'support_email' => $organizer->getEmail(), ]); } + + private function createDefaultProductCategory(EventDomainObject $event): void + { + $this->createProductCategoryService->createCategory( + name: __('Tickets'), + isHidden: false, + eventId: $event->getId(), + description: null, + noProductsMessage: __('There are no tickets available for this event.'), + ); + } } diff --git a/backend/app/Services/Domain/Event/DTO/DuplicateEventDataDTO.php b/backend/app/Services/Domain/Event/DTO/DuplicateEventDataDTO.php index 83262ea5e8..b06047234b 100644 --- a/backend/app/Services/Domain/Event/DTO/DuplicateEventDataDTO.php +++ b/backend/app/Services/Domain/Event/DTO/DuplicateEventDataDTO.php @@ -11,7 +11,7 @@ public function __construct( public int $accountId, public string $title, public string $startDate, - public bool $duplicateTickets = true, + public bool $duplicateProducts = true, public bool $duplicateQuestions = true, public bool $duplicateSettings = true, public bool $duplicatePromoCodes = true, diff --git a/backend/app/Services/Domain/Event/DTO/EventDailyStatsResponseDTO.php b/backend/app/Services/Domain/Event/DTO/EventDailyStatsResponseDTO.php index f6d88472de..e15c8fd1c5 100644 --- a/backend/app/Services/Domain/Event/DTO/EventDailyStatsResponseDTO.php +++ b/backend/app/Services/Domain/Event/DTO/EventDailyStatsResponseDTO.php @@ -9,8 +9,11 @@ public function __construct( public float $total_fees, public float $total_tax, public float $total_sales_gross, - public int $tickets_sold, + public int $products_sold, public int $orders_created, + public int $attendees_registered, + public float $total_refunded, + ) { } diff --git a/backend/app/Services/Domain/Event/DuplicateEventService.php b/backend/app/Services/Domain/Event/DuplicateEventService.php index 96b7112a47..96dffb1782 100644 --- a/backend/app/Services/Domain/Event/DuplicateEventService.php +++ b/backend/app/Services/Domain/Event/DuplicateEventService.php @@ -12,8 +12,8 @@ use HiEvents\DomainObjects\QuestionDomainObject; use HiEvents\DomainObjects\Status\EventStatus; use HiEvents\DomainObjects\TaxAndFeesDomainObject; -use HiEvents\DomainObjects\TicketDomainObject; -use HiEvents\DomainObjects\TicketPriceDomainObject; +use HiEvents\DomainObjects\ProductDomainObject; +use HiEvents\DomainObjects\ProductPriceDomainObject; use HiEvents\Repository\Eloquent\Value\Relationship; use HiEvents\Repository\Interfaces\EventRepositoryInterface; use HiEvents\Repository\Interfaces\ImageRepositoryInterface; @@ -21,7 +21,7 @@ use HiEvents\Services\Domain\CheckInList\CreateCheckInListService; use HiEvents\Services\Domain\PromoCode\CreatePromoCodeService; use HiEvents\Services\Domain\Question\CreateQuestionService; -use HiEvents\Services\Domain\Ticket\CreateTicketService; +use HiEvents\Services\Domain\Product\CreateProductService; use HTMLPurifier; use Illuminate\Database\DatabaseManager; use Throwable; @@ -31,7 +31,7 @@ class DuplicateEventService public function __construct( private readonly EventRepositoryInterface $eventRepository, private readonly CreateEventService $createEventService, - private readonly CreateTicketService $createTicketService, + private readonly CreateProductService $createProductService, private readonly CreateQuestionService $createQuestionService, private readonly CreatePromoCodeService $createPromoCodeService, private readonly CreateCapacityAssignmentService $createCapacityAssignmentService, @@ -51,7 +51,7 @@ public function duplicateEvent( string $accountId, string $title, string $startDate, - bool $duplicateTickets = true, + bool $duplicateProducts = true, bool $duplicateQuestions = true, bool $duplicateSettings = true, bool $duplicatePromoCodes = true, @@ -79,8 +79,8 @@ public function duplicateEvent( cloneEventSettings: $duplicateSettings, ); - if ($duplicateTickets) { - $this->cloneExistingTickets( + if ($duplicateProducts) { + $this->cloneExistingProducts( event: $event, newEventId: $newEvent->getId(), duplicateQuestions: $duplicateQuestions, @@ -131,7 +131,7 @@ private function cloneExistingEvent(EventDomainObject $event, bool $cloneEventSe /** * @throws Throwable */ - private function cloneExistingTickets( + private function cloneExistingProducts( EventDomainObject $event, int $newEventId, bool $duplicateQuestions, @@ -140,41 +140,41 @@ private function cloneExistingTickets( bool $duplicateCheckInLists, ): array { - $oldTicketToNewTicketMap = []; + $oldProductToNewProductMap = []; - foreach ($event->getTickets() as $ticket) { - $ticket->setEventId($newEventId); - $newTicket = $this->createTicketService->createTicket( - ticket: $ticket, + foreach ($event->getProducts() as $product) { + $product->setEventId($newEventId); + $newProduct = $this->createProductService->createProduct( + product: $product, accountId: $event->getAccountId(), - taxAndFeeIds: $ticket->getTaxAndFees()?->map(fn($taxAndFee) => $taxAndFee->getId())?->toArray(), + taxAndFeeIds: $product->getTaxAndFees()?->map(fn($taxAndFee) => $taxAndFee->getId())?->toArray(), ); - $oldTicketToNewTicketMap[$ticket->getId()] = $newTicket->getId(); + $oldProductToNewProductMap[$product->getId()] = $newProduct->getId(); } if ($duplicateQuestions) { - $this->cloneQuestions($event, $newEventId, $oldTicketToNewTicketMap); + $this->cloneQuestions($event, $newEventId, $oldProductToNewProductMap); } if ($duplicatePromoCodes) { - $this->clonePromoCodes($event, $newEventId, $oldTicketToNewTicketMap); + $this->clonePromoCodes($event, $newEventId, $oldProductToNewProductMap); } if ($duplicateCapacityAssignments) { - $this->cloneCapacityAssignments($event, $newEventId, $oldTicketToNewTicketMap); + $this->cloneCapacityAssignments($event, $newEventId, $oldProductToNewProductMap); } if ($duplicateCheckInLists) { - $this->cloneCheckInLists($event, $newEventId, $oldTicketToNewTicketMap); + $this->cloneCheckInLists($event, $newEventId, $oldProductToNewProductMap); } - return $oldTicketToNewTicketMap; + return $oldProductToNewProductMap; } /** * @throws Throwable */ - private function cloneQuestions(EventDomainObject $event, int $newEventId, array $oldTicketToNewTicketMap): void + private function cloneQuestions(EventDomainObject $event, int $newEventId, array $oldProductToNewProductMap): void { foreach ($event->getQuestions() as $question) { $this->createQuestionService->createQuestion( @@ -187,8 +187,8 @@ private function cloneQuestions(EventDomainObject $event, int $newEventId, array ->setOptions($question->getOptions()) ->setIsHidden($question->getIsHidden()), array_map( - static fn(TicketDomainObject $ticket) => $oldTicketToNewTicketMap[$ticket->getId()], - $question->getTickets()?->all(), + static fn(ProductDomainObject $product) => $oldProductToNewProductMap[$product->getId()], + $question->getProducts()?->all(), ), ); } @@ -197,16 +197,16 @@ private function cloneQuestions(EventDomainObject $event, int $newEventId, array /** * @throws Throwable */ - private function clonePromoCodes(EventDomainObject $event, int $newEventId, array $oldTicketToNewTicketMap): void + private function clonePromoCodes(EventDomainObject $event, int $newEventId, array $oldProductToNewProductMap): void { foreach ($event->getPromoCodes() as $promoCode) { $this->createPromoCodeService->createPromoCode( (new PromoCodeDomainObject()) ->setCode($promoCode->getCode()) ->setEventId($newEventId) - ->setApplicableTicketIds(array_map( - static fn($ticketId) => $oldTicketToNewTicketMap[$ticketId], - $promoCode->getApplicableTicketIds() ?? [], + ->setApplicableProductIds(array_map( + static fn($productId) => $oldProductToNewProductMap[$productId], + $promoCode->getApplicableProductIds() ?? [], )) ->setDiscountType($promoCode->getDiscountType()) ->setDiscount($promoCode->getDiscount()) @@ -216,7 +216,7 @@ private function clonePromoCodes(EventDomainObject $event, int $newEventId, arra } } - private function cloneCapacityAssignments(EventDomainObject $event, int $newEventId, $oldTicketToNewTicketMap): void + private function cloneCapacityAssignments(EventDomainObject $event, int $newEventId, $oldProductToNewProductMap): void { /** @var CapacityAssignmentDomainObject $capacityAssignment */ foreach ($event->getCapacityAssignments() as $capacityAssignment) { @@ -227,13 +227,13 @@ private function cloneCapacityAssignments(EventDomainObject $event, int $newEven ->setCapacity($capacityAssignment->getCapacity()) ->setAppliesTo($capacityAssignment->getAppliesTo()) ->setStatus($capacityAssignment->getStatus()), - ticketIds: $capacityAssignment->getTickets() - ?->map(fn($ticket) => $oldTicketToNewTicketMap[$ticket->getId()])?->toArray() ?? [], + productIds: $capacityAssignment->getProducts() + ?->map(fn($product) => $oldProductToNewProductMap[$product->getId()])?->toArray() ?? [], ); } } - private function cloneCheckInLists(EventDomainObject $event, int $newEventId, $oldTicketToNewTicketMap): void + private function cloneCheckInLists(EventDomainObject $event, int $newEventId, $oldProductToNewProductMap): void { foreach ($event->getCheckInLists() as $checkInList) { $this->createCheckInListService->createCheckInList( @@ -243,8 +243,8 @@ private function cloneCheckInLists(EventDomainObject $event, int $newEventId, $o ->setExpiresAt($checkInList->getExpiresAt()) ->setActivatesAt($checkInList->getActivatesAt()) ->setEventId($newEventId), - ticketIds: $checkInList->getTickets() - ?->map(fn($ticket) => $oldTicketToNewTicketMap[$ticket->getId()])?->toArray() ?? [], + productIds: $checkInList->getProducts() + ?->map(fn($product) => $oldProductToNewProductMap[$product->getId()])?->toArray() ?? [], ); } } @@ -272,20 +272,20 @@ private function getEventWithRelations(string $eventId, string $accountId): Even return $this->eventRepository ->loadRelation(EventSettingDomainObject::class) ->loadRelation( - new Relationship(TicketDomainObject::class, [ - new Relationship(TicketPriceDomainObject::class), + new Relationship(ProductDomainObject::class, [ + new Relationship(ProductPriceDomainObject::class), new Relationship(TaxAndFeesDomainObject::class) ]) ) ->loadRelation(PromoCodeDomainObject::class) ->loadRelation(new Relationship(QuestionDomainObject::class, [ - new Relationship(TicketDomainObject::class), + new Relationship(ProductDomainObject::class), ])) ->loadRelation(new Relationship(CapacityAssignmentDomainObject::class, [ - new Relationship(TicketDomainObject::class), + new Relationship(ProductDomainObject::class), ])) ->loadRelation(new Relationship(CheckInListDomainObject::class, [ - new Relationship(TicketDomainObject::class), + new Relationship(ProductDomainObject::class), ])) ->loadRelation(ImageDomainObject::class) ->findFirstWhere([ diff --git a/backend/app/Services/Domain/Event/EventStatsFetchService.php b/backend/app/Services/Domain/Event/EventStatsFetchService.php index 5344d7e4fd..ce4263eb87 100644 --- a/backend/app/Services/Domain/Event/EventStatsFetchService.php +++ b/backend/app/Services/Domain/Event/EventStatsFetchService.php @@ -25,12 +25,15 @@ public function getEventStats(EventStatsRequestDTO $requestData): EventStatsResp // Aggregate total statistics for the event for all time $totalsQuery = <<start_date, end_date: $requestData->end_date, check_in_stats: $this->getCheckedInStats($eventId), - total_tickets_sold: $totalsResult->total_tickets_sold ?? 0, + total_products_sold: $totalsResult->total_products_sold ?? 0, + total_attendees_registered: $totalsResult->attendees_registered ?? 0, total_orders: $totalsResult->total_orders ?? 0, total_gross_sales: $totalsResult->total_gross_sales ?? 0, total_fees: $totalsResult->total_fees ?? 0, total_tax: $totalsResult->total_tax ?? 0, total_views: $totalsResult->total_views ?? 0, - + total_refunded: $totalsResult->total_refunded ?? 0, ); } @@ -77,7 +81,9 @@ public function getDailyEventStats(EventStatsRequestDTO $requestData): Collectio COALESCE(SUM(eds.total_tax), 0) AS total_tax, COALESCE(SUM(eds.sales_total_gross), 0) AS total_sales_gross, COALESCE(SUM(eds.orders_created), 0) AS orders_created, - COALESCE(SUM(eds.tickets_sold), 0) AS tickets_sold + COALESCE(SUM(eds.products_sold), 0) AS products_sold, + COALESCE(SUM(eds.attendees_registered), 0) AS attendees_registered, + COALESCE(SUM(eds.total_refunded), 0) AS total_refunded FROM date_series ds LEFT JOIN event_daily_statistics eds ON ds.date = eds.date AND eds.deleted_at IS NULL AND eds.event_id = :eventId GROUP BY ds.date @@ -100,8 +106,10 @@ public function getDailyEventStats(EventStatsRequestDTO $requestData): Collectio total_fees: $result->total_fees, total_tax: $result->total_tax, total_sales_gross: $result->total_sales_gross, - tickets_sold: $result->tickets_sold, + products_sold: $result->products_sold, orders_created: $result->orders_created, + attendees_registered: $result->attendees_registered, + total_refunded: $result->total_refunded, ); }); } diff --git a/backend/app/Services/Domain/EventStatistics/EventStatisticsUpdateService.php b/backend/app/Services/Domain/EventStatistics/EventStatisticsUpdateService.php index 7da0c4bfd3..f62b1ede44 100644 --- a/backend/app/Services/Domain/EventStatistics/EventStatisticsUpdateService.php +++ b/backend/app/Services/Domain/EventStatistics/EventStatisticsUpdateService.php @@ -2,16 +2,16 @@ namespace HiEvents\Services\Domain\EventStatistics; +use HiEvents\DomainObjects\Generated\ProductDomainObjectAbstract; use HiEvents\DomainObjects\Generated\PromoCodeDomainObjectAbstract; -use HiEvents\DomainObjects\Generated\TicketDomainObjectAbstract; use HiEvents\DomainObjects\OrderDomainObject; use HiEvents\DomainObjects\OrderItemDomainObject; use HiEvents\Exceptions\EventStatisticsVersionMismatchException; use HiEvents\Repository\Interfaces\EventDailyStatisticRepositoryInterface; use HiEvents\Repository\Interfaces\EventStatisticRepositoryInterface; use HiEvents\Repository\Interfaces\OrderRepositoryInterface; +use HiEvents\Repository\Interfaces\ProductRepositoryInterface; use HiEvents\Repository\Interfaces\PromoCodeRepositoryInterface; -use HiEvents\Repository\Interfaces\TicketRepositoryInterface; use HiEvents\Values\MoneyValue; use Illuminate\Database\DatabaseManager; use Illuminate\Support\Carbon; @@ -26,7 +26,7 @@ { public function __construct( private PromoCodeRepositoryInterface $promoCodeRepository, - private TicketRepositoryInterface $ticketRepository, + private ProductRepositoryInterface $productRepository, private EventStatisticRepositoryInterface $eventStatisticsRepository, private EventDailyStatisticRepositoryInterface $eventDailyStatisticRepository, private DatabaseManager $databaseManager, @@ -50,7 +50,7 @@ public function updateStatistics(OrderDomainObject $order): void $this->updateEventStats($order); $this->updateEventDailyStats($order); $this->updatePromoCodeCounts($order); - $this->updateTicketStatistics($order); + $this->updateProductStatistics($order); }); } @@ -126,12 +126,12 @@ private function updatePromoCodeCounts(OrderDomainObject $order): void } } - private function updateTicketStatistics(OrderDomainObject $order): void + private function updateProductStatistics(OrderDomainObject $order): void { foreach ($order->getOrderItems() as $orderItem) { - $this->ticketRepository->increment( - $orderItem->getTicketId(), - TicketDomainObjectAbstract::SALES_VOLUME, + $this->productRepository->increment( + $orderItem->getProductId(), + ProductDomainObjectAbstract::SALES_VOLUME, $orderItem->getTotalBeforeAdditions(), ); } @@ -153,7 +153,9 @@ private function updateEventStats(OrderDomainObject $order): void if ($eventStatistics === null) { $this->eventStatisticsRepository->create([ 'event_id' => $order->getEventId(), - 'tickets_sold' => $order->getOrderItems() + 'products_sold' => $order->getOrderItems() + ?->sum(fn(OrderItemDomainObject $orderItem) => $orderItem->getQuantity()), + 'attendees_registered' => $order->getTicketOrderItems() ?->sum(fn(OrderItemDomainObject $orderItem) => $orderItem->getQuantity()), 'sales_total_gross' => $order->getTotalGross(), 'sales_total_before_additions' => $order->getTotalBeforeAdditions(), @@ -167,7 +169,9 @@ private function updateEventStats(OrderDomainObject $order): void $update = $this->eventStatisticsRepository->updateWhere( attributes: [ - 'tickets_sold' => $eventStatistics->getTicketsSold() + $order->getOrderItems() + 'products_sold' => $eventStatistics->getProductsSold() + $order->getOrderItems() + ?->sum(fn(OrderItemDomainObject $orderItem) => $orderItem->getQuantity()), + 'attendees_registered' => $eventStatistics->getAttendeesRegistered() + $order->getTicketOrderItems() ?->sum(fn(OrderItemDomainObject $orderItem) => $orderItem->getQuantity()), 'sales_total_gross' => $eventStatistics->getSalesTotalGross() + $order->getTotalGross(), 'sales_total_before_additions' => $eventStatistics->getSalesTotalBeforeAdditions() + $order->getTotalBeforeAdditions(), @@ -208,7 +212,8 @@ private function updateEventDailyStats(OrderDomainObject $order): void $this->eventDailyStatisticRepository->create([ 'event_id' => $order->getEventId(), 'date' => (new Carbon($order->getCreatedAt()))->format('Y-m-d'), - 'tickets_sold' => $order->getOrderItems()?->sum(fn(OrderItemDomainObject $orderItem) => $orderItem->getQuantity()), + 'products_sold' => $order->getOrderItems()?->sum(fn(OrderItemDomainObject $orderItem) => $orderItem->getQuantity()), + 'attendees_registered' => $order->getTicketOrderItems()?->sum(fn(OrderItemDomainObject $orderItem) => $orderItem->getQuantity()), 'sales_total_gross' => $order->getTotalGross(), 'sales_total_before_additions' => $order->getTotalBeforeAdditions(), 'total_tax' => $order->getTotalTax(), @@ -220,7 +225,8 @@ private function updateEventDailyStats(OrderDomainObject $order): void $update = $this->eventDailyStatisticRepository->updateWhere( attributes: [ - 'tickets_sold' => $eventDailyStatistic->getTicketsSold() + $order->getOrderItems()->sum(fn(OrderItemDomainObject $orderItem) => $orderItem->getQuantity()), + 'attendees_registered' => $eventDailyStatistic->getAttendeesRegistered() + $order->getTicketOrderItems()->sum(fn(OrderItemDomainObject $orderItem) => $orderItem->getQuantity()), + 'products_sold' => $eventDailyStatistic->getProductsSold() + $order->getOrderItems()->sum(fn(OrderItemDomainObject $orderItem) => $orderItem->getQuantity()), 'sales_total_gross' => $eventDailyStatistic->getSalesTotalGross() + $order->getTotalGross(), 'sales_total_before_additions' => $eventDailyStatistic->getSalesTotalBeforeAdditions() + $order->getTotalBeforeAdditions(), 'total_tax' => $eventDailyStatistic->getTotalTax() + $order->getTotalTax(), diff --git a/backend/app/Services/Domain/Mail/SendEventEmailMessagesService.php b/backend/app/Services/Domain/Mail/SendEventEmailMessagesService.php index f4a6b05c85..8ab6cbda5f 100644 --- a/backend/app/Services/Domain/Mail/SendEventEmailMessagesService.php +++ b/backend/app/Services/Domain/Mail/SendEventEmailMessagesService.php @@ -70,8 +70,8 @@ public function send(SendMessageDTO $messageData): void case MessageTypeEnum::ORDER: $this->sendOrderMessages($messageData, $event, $order); break; - case MessageTypeEnum::TICKET: - $this->sendTicketMessages($messageData, $event); + case MessageTypeEnum::PRODUCT: + $this->sendProductMessages($messageData, $event); break; case MessageTypeEnum::EVENT: $this->sendEventMessages($messageData, $event); @@ -95,11 +95,11 @@ private function sendAttendeeMessages(SendMessageDTO $messageData, EventDomainOb $this->emailAttendees($attendees, $messageData, $event); } - private function sendTicketMessages(SendMessageDTO $messageData, EventDomainObject $event): void + private function sendProductMessages(SendMessageDTO $messageData, EventDomainObject $event): void { $attendees = $this->attendeeRepository->findWhereIn( - field: 'ticket_id', - values: $messageData->ticket_ids, + field: 'product_id', + values: $messageData->product_ids, additionalWhere: [ 'event_id' => $messageData->event_id, 'status' => AttendeeStatus::ACTIVE->name, diff --git a/backend/app/Services/Domain/Mail/SendOrderDetailsService.php b/backend/app/Services/Domain/Mail/SendOrderDetailsService.php index 62040573f9..acf9caf9e6 100644 --- a/backend/app/Services/Domain/Mail/SendOrderDetailsService.php +++ b/backend/app/Services/Domain/Mail/SendOrderDetailsService.php @@ -23,12 +23,12 @@ public function __construct( private EventRepositoryInterface $eventRepository, private OrderRepositoryInterface $orderRepository, private Mailer $mailer, - private SendAttendeeTicketService $sendAttendeeTicketService, + private SendAttendeeTicketService $sendAttendeeProductService, ) { } - public function sendOrderSummaryAndTicketEmails(OrderDomainObject $order): void + public function sendOrderSummaryAndProductEmails(OrderDomainObject $order): void { $order = $this->orderRepository ->loadRelation(OrderItemDomainObject::class) @@ -42,7 +42,7 @@ public function sendOrderSummaryAndTicketEmails(OrderDomainObject $order): void if ($order->isOrderCompleted()) { $this->sendOrderSummaryEmails($order, $event); - $this->sendAttendeeTicketEmails($order, $event); + $this->sendAttendeeProductEmails($order, $event); } if ($order->isOrderFailed()) { @@ -57,7 +57,7 @@ public function sendOrderSummaryAndTicketEmails(OrderDomainObject $order): void } } - private function sendAttendeeTicketEmails(OrderDomainObject $order, EventDomainObject $event): void + private function sendAttendeeProductEmails(OrderDomainObject $order, EventDomainObject $event): void { $sentEmails = []; foreach ($order->getAttendees() as $attendee) { @@ -65,7 +65,7 @@ private function sendAttendeeTicketEmails(OrderDomainObject $order, EventDomainO continue; } - $this->sendAttendeeTicketService->send( + $this->sendAttendeeProductService->send( attendee: $attendee, event: $event, eventSettings: $event->getEventSettings(), diff --git a/backend/app/Services/Domain/Order/OrderCancelService.php b/backend/app/Services/Domain/Order/OrderCancelService.php index b9e1125d82..3455cec02e 100644 --- a/backend/app/Services/Domain/Order/OrderCancelService.php +++ b/backend/app/Services/Domain/Order/OrderCancelService.php @@ -11,7 +11,7 @@ use HiEvents\Repository\Interfaces\AttendeeRepositoryInterface; use HiEvents\Repository\Interfaces\EventRepositoryInterface; use HiEvents\Repository\Interfaces\OrderRepositoryInterface; -use HiEvents\Services\Domain\Ticket\TicketQuantityUpdateService; +use HiEvents\Services\Domain\Product\ProductQuantityUpdateService; use Illuminate\Contracts\Mail\Mailer; use Illuminate\Database\DatabaseManager; use Throwable; @@ -19,12 +19,12 @@ readonly class OrderCancelService { public function __construct( - private Mailer $mailer, - private AttendeeRepositoryInterface $attendeeRepository, - private EventRepositoryInterface $eventRepository, - private OrderRepositoryInterface $orderRepository, - private DatabaseManager $databaseManager, - private TicketQuantityUpdateService $ticketQuantityService, + private Mailer $mailer, + private AttendeeRepositoryInterface $attendeeRepository, + private EventRepositoryInterface $eventRepository, + private OrderRepositoryInterface $orderRepository, + private DatabaseManager $databaseManager, + private ProductQuantityUpdateService $productQuantityService, ) { } @@ -36,7 +36,7 @@ public function cancelOrder(OrderDomainObject $order): void { $this->databaseManager->transaction(function () use ($order) { $this->cancelAttendees($order); - $this->adjustTicketQuantities($order); + $this->adjustProductQuantities($order); $this->updateOrderStatus($order); $event = $this->eventRepository @@ -66,18 +66,18 @@ private function cancelAttendees(OrderDomainObject $order): void ); } - private function adjustTicketQuantities(OrderDomainObject $order): void + private function adjustProductQuantities(OrderDomainObject $order): void { $attendees = $this->attendeeRepository->findWhere([ 'order_id' => $order->getId(), 'status' => AttendeeStatus::ACTIVE->name, ]); - $ticketIdCountMap = $attendees - ->map(fn(AttendeeDomainObject $attendee) => $attendee->getTicketPriceId())->countBy(); + $productIdCountMap = $attendees + ->map(fn(AttendeeDomainObject $attendee) => $attendee->getProductPriceId())->countBy(); - foreach ($ticketIdCountMap as $ticketPriceId => $count) { - $this->ticketQuantityService->decreaseQuantitySold($ticketPriceId, $count); + foreach ($productIdCountMap as $productPriceId => $count) { + $this->productQuantityService->decreaseQuantitySold($productPriceId, $count); } } diff --git a/backend/app/Services/Domain/Order/OrderCreateRequestValidationService.php b/backend/app/Services/Domain/Order/OrderCreateRequestValidationService.php index b55ad8c76e..f075f65b98 100644 --- a/backend/app/Services/Domain/Order/OrderCreateRequestValidationService.php +++ b/backend/app/Services/Domain/Order/OrderCreateRequestValidationService.php @@ -4,18 +4,18 @@ use Exception; use HiEvents\DomainObjects\CapacityAssignmentDomainObject; -use HiEvents\DomainObjects\Enums\TicketType; +use HiEvents\DomainObjects\Enums\ProductPriceType; use HiEvents\DomainObjects\EventDomainObject; use HiEvents\DomainObjects\Generated\PromoCodeDomainObjectAbstract; -use HiEvents\DomainObjects\TicketDomainObject; -use HiEvents\DomainObjects\TicketPriceDomainObject; +use HiEvents\DomainObjects\ProductDomainObject; +use HiEvents\DomainObjects\ProductPriceDomainObject; use HiEvents\Helper\Currency; use HiEvents\Repository\Interfaces\EventRepositoryInterface; use HiEvents\Repository\Interfaces\PromoCodeRepositoryInterface; -use HiEvents\Repository\Interfaces\TicketRepositoryInterface; -use HiEvents\Services\Domain\Ticket\AvailableTicketQuantitiesFetchService; -use HiEvents\Services\Domain\Ticket\DTO\AvailableTicketQuantitiesDTO; -use HiEvents\Services\Domain\Ticket\DTO\AvailableTicketQuantitiesResponseDTO; +use HiEvents\Repository\Interfaces\ProductRepositoryInterface; +use HiEvents\Services\Domain\Product\AvailableProductQuantitiesFetchService; +use HiEvents\Services\Domain\Product\DTO\AvailableProductQuantitiesDTO; +use HiEvents\Services\Domain\Product\DTO\AvailableProductQuantitiesResponseDTO; use Illuminate\Support\Collection; use Illuminate\Support\Facades\Validator; use Illuminate\Validation\ValidationException; @@ -23,13 +23,13 @@ class OrderCreateRequestValidationService { - private AvailableTicketQuantitiesResponseDTO $availableTicketQuantities; + private AvailableProductQuantitiesResponseDTO $availableProductQuantities; public function __construct( - readonly private TicketRepositoryInterface $ticketRepository, - readonly private PromoCodeRepositoryInterface $promoCodeRepository, - readonly private EventRepositoryInterface $eventRepository, - readonly private AvailableTicketQuantitiesFetchService $fetchAvailableTicketQuantitiesService, + readonly private ProductRepositoryInterface $productRepository, + readonly private PromoCodeRepositoryInterface $promoCodeRepository, + readonly private EventRepositoryInterface $eventRepository, + readonly private AvailableProductQuantitiesFetchService $fetchAvailableProductQuantitiesService, ) { } @@ -44,16 +44,16 @@ public function validateRequestData(int $eventId, array $data = []): void $event = $this->eventRepository->findById($eventId); $this->validatePromoCode($eventId, $data); - $this->validateTicketSelection($data); + $this->validateProductSelection($data); - $this->availableTicketQuantities = $this->fetchAvailableTicketQuantitiesService - ->getAvailableTicketQuantities( + $this->availableProductQuantities = $this->fetchAvailableProductQuantitiesService + ->getAvailableProductQuantities( $event->getId(), ignoreCache: true, ); $this->validateOverallCapacity($data); - $this->validateTicketDetails($event, $data); + $this->validateProductDetails($event, $data); } /** @@ -81,12 +81,12 @@ private function validatePromoCode(int $eventId, array $data): void private function validateTypes(array $data): void { $validator = Validator::make($data, [ - 'tickets' => 'required|array', - 'tickets.*.ticket_id' => 'required|integer', - 'tickets.*.quantities' => 'required|array', - 'tickets.*.quantities.*.quantity' => 'required|integer', - 'tickets.*.quantities.*.price_id' => 'required|integer', - 'tickets.*.quantities.*.price' => 'numeric|min:0', + 'products' => 'required|array', + 'products.*.product_id' => 'required|integer', + 'products.*.quantities' => 'required|array', + 'products.*.quantities.*.quantity' => 'required|integer', + 'products.*.quantities.*.price_id' => 'required|integer', + 'products.*.quantities.*.price' => 'numeric|min:0', ]); if ($validator->fails()) { @@ -97,12 +97,12 @@ private function validateTypes(array $data): void /** * @throws ValidationException */ - private function validateTicketSelection(array $data): void + private function validateProductSelection(array $data): void { - $ticketData = collect($data['tickets']); - if ($ticketData->isEmpty() || $ticketData->sum(fn($ticket) => collect($ticket['quantities'])->sum('quantity')) === 0) { + $productData = collect($data['products']); + if ($productData->isEmpty() || $productData->sum(fn($product) => collect($product['quantities'])->sum('quantity')) === 0) { throw ValidationException::withMessages([ - 'tickets' => __('You haven\'t selected any tickets') + 'products' => __('You haven\'t selected any products') ]); } } @@ -110,150 +110,150 @@ private function validateTicketSelection(array $data): void /** * @throws Exception */ - private function getTickets(array $data): Collection + private function getProducts(array $data): Collection { - $ticketIds = collect($data['tickets'])->pluck('ticket_id'); - return $this->ticketRepository - ->loadRelation(TicketPriceDomainObject::class) - ->findWhereIn('id', $ticketIds->toArray()); + $productIds = collect($data['products'])->pluck('product_id'); + return $this->productRepository + ->loadRelation(ProductPriceDomainObject::class) + ->findWhereIn('id', $productIds->toArray()); } /** * @throws ValidationException * @throws Exception */ - private function validateTicketDetails(EventDomainObject $event, array $data): void + private function validateProductDetails(EventDomainObject $event, array $data): void { - $tickets = $this->getTickets($data); + $products = $this->getProducts($data); - foreach ($data['tickets'] as $ticketIndex => $ticketAndQuantities) { - $this->validateSingleTicketDetails($event, $ticketIndex, $ticketAndQuantities, $tickets); + foreach ($data['products'] as $productIndex => $productAndQuantities) { + $this->validateSingleProductDetails($event, $productIndex, $productAndQuantities, $products); } } /** * @throws ValidationException */ - private function validateSingleTicketDetails(EventDomainObject $event, int $ticketIndex, array $ticketAndQuantities, $tickets): void + private function validateSingleProductDetails(EventDomainObject $event, int $productIndex, array $productAndQuantities, $products): void { - $ticketId = $ticketAndQuantities['ticket_id']; - $totalQuantity = collect($ticketAndQuantities['quantities'])->sum('quantity'); + $productId = $productAndQuantities['product_id']; + $totalQuantity = collect($productAndQuantities['quantities'])->sum('quantity'); if ($totalQuantity === 0) { return; } - /** @var TicketDomainObject $ticket */ - $ticket = $tickets->filter(fn($t) => $t->getId() === $ticketId)->first(); - if (!$ticket) { - throw new NotFoundHttpException(sprintf('Ticket ID %d not found', $ticketId)); + /** @var ProductDomainObject $product */ + $product = $products->filter(fn($t) => $t->getId() === $productId)->first(); + if (!$product) { + throw new NotFoundHttpException(sprintf('Product ID %d not found', $productId)); } - $this->validateTicketEvent( + $this->validateProductEvent( event: $event, - ticketId: $ticketId, - ticket: $ticket + productId: $productId, + product: $product ); - $this->validateTicketQuantity( - ticketIndex: $ticketIndex, - ticketAndQuantities: $ticketAndQuantities, - ticket: $ticket + $this->validateProductQuantity( + productIndex: $productIndex, + productAndQuantities: $productAndQuantities, + product: $product ); - $this->validateTicketTypeAndPrice( + $this->validateProductTypeAndPrice( event: $event, - ticketIndex: $ticketIndex, - ticketAndQuantities: $ticketAndQuantities, - ticket: $ticket + productIndex: $productIndex, + productAndQuantities: $productAndQuantities, + product: $product ); - $this->validateSoldOutTickets( - ticketId: $ticketId, - ticketIndex: $ticketIndex, - ticket: $ticket + $this->validateSoldOutProducts( + productId: $productId, + productIndex: $productIndex, + product: $product ); $this->validatePriceIdAndQuantity( - ticketIndex: $ticketIndex, - ticketAndQuantities: $ticketAndQuantities, - ticket: $ticket + productIndex: $productIndex, + productAndQuantities: $productAndQuantities, + product: $product ); } /** * @throws ValidationException */ - private function validateTicketQuantity(int $ticketIndex, array $ticketAndQuantities, TicketDomainObject $ticket): void + private function validateProductQuantity(int $productIndex, array $productAndQuantities, ProductDomainObject $product): void { - $totalQuantity = collect($ticketAndQuantities['quantities'])->sum('quantity'); - $maxPerOrder = (int)$ticket->getMaxPerOrder() ?: 100; + $totalQuantity = collect($productAndQuantities['quantities'])->sum('quantity'); + $maxPerOrder = (int)$product->getMaxPerOrder() ?: 100; - $capacityMaximum = $this->availableTicketQuantities - ->ticketQuantities - ->where('ticket_id', $ticket->getId()) - ->map(fn(AvailableTicketQuantitiesDTO $price) => $price->capacities) + $capacityMaximum = $this->availableProductQuantities + ->productQuantities + ->where('product_id', $product->getId()) + ->map(fn(AvailableProductQuantitiesDTO $price) => $price->capacities) ->flatten() ->min(fn(CapacityAssignmentDomainObject $capacity) => $capacity->getCapacity()); - $ticketAvailableQuantity = $this->availableTicketQuantities - ->ticketQuantities - ->first(fn(AvailableTicketQuantitiesDTO $price) => $price->ticket_id === $ticket->getId()) + $productAvailableQuantity = $this->availableProductQuantities + ->productQuantities + ->first(fn(AvailableProductQuantitiesDTO $price) => $price->product_id === $product->getId()) ->quantity_available; - # if there are fewer tickets available than the configured minimum, we allow less than the minimum to be purchased - $minPerOrder = min((int)$ticket->getMinPerOrder() ?: 1, + # if there are fewer products available than the configured minimum, we allow less than the minimum to be purchased + $minPerOrder = min((int)$product->getMinPerOrder() ?: 1, $capacityMaximum ?: $maxPerOrder, - $ticketAvailableQuantity ?: $maxPerOrder); + $productAvailableQuantity ?: $maxPerOrder); - $this->validateTicketPricesQuantity( - quantities: $ticketAndQuantities['quantities'], - ticket: $ticket, - ticketIndex: $ticketIndex + $this->validateProductPricesQuantity( + quantities: $productAndQuantities['quantities'], + product: $product, + productIndex: $productIndex ); if ($totalQuantity > $maxPerOrder) { throw ValidationException::withMessages([ - "tickets.$ticketIndex" => __("The maximum number of tickets available for :tickets is :max", [ + "products.$productIndex" => __("The maximum number of products available for :products is :max", [ 'max' => $maxPerOrder, - 'ticket' => $ticket->getTitle(), + 'product' => $product->getTitle(), ]), ]); } if ($totalQuantity < $minPerOrder) { throw ValidationException::withMessages([ - "tickets.$ticketIndex" => __("You must order at least :min tickets for :ticket", [ + "products.$productIndex" => __("You must order at least :min products for :product", [ 'min' => $minPerOrder, - 'ticket' => $ticket->getTitle(), + 'product' => $product->getTitle(), ]), ]); } } - private function validateTicketEvent(EventDomainObject $event, int $ticketId, TicketDomainObject $ticket): void + private function validateProductEvent(EventDomainObject $event, int $productId, ProductDomainObject $product): void { - if ($ticket->getEventId() !== $event->getId()) { - throw new NotFoundHttpException(sprintf('Ticket ID %d not found for event ID %d', $ticketId, $event->getId())); + if ($product->getEventId() !== $event->getId()) { + throw new NotFoundHttpException(sprintf('Product ID %d not found for event ID %d', $productId, $event->getId())); } } /** * @throws ValidationException */ - private function validateTicketTypeAndPrice( + private function validateProductTypeAndPrice( EventDomainObject $event, - int $ticketIndex, - array $ticketAndQuantities, - TicketDomainObject $ticket + int $productIndex, + array $productAndQuantities, + ProductDomainObject $product ): void { - if ($ticket->getType() === TicketType::DONATION->name) { - $price = $ticketAndQuantities['quantities'][0]['price'] ?? 0; - if ($price < $ticket->getPrice()) { - $formattedPrice = Currency::format($ticket->getPrice(), $event->getCurrency()); + if ($product->getType() === ProductPriceType::DONATION->name) { + $price = $productAndQuantities['quantities'][0]['price'] ?? 0; + if ($price < $product->getPrice()) { + $formattedPrice = Currency::format($product->getPrice(), $event->getCurrency()); throw ValidationException::withMessages([ - "tickets.$ticketIndex.quantities.0.price" => __("The minimum amount is :price", ['price' => $formattedPrice]), + "products.$productIndex.quantities.0.price" => __("The minimum amount is :price", ['price' => $formattedPrice]), ]); } } @@ -262,13 +262,13 @@ private function validateTicketTypeAndPrice( /** * @throws ValidationException */ - private function validateSoldOutTickets(int $ticketId, int $ticketIndex, TicketDomainObject $ticket): void + private function validateSoldOutProducts(int $productId, int $productIndex, ProductDomainObject $product): void { - if ($ticket->isSoldOut()) { + if ($product->isSoldOut()) { throw ValidationException::withMessages([ - "tickets.$ticketIndex" => __("The ticket :ticket is sold out", [ - 'id' => $ticketId, - 'ticket' => $ticket->getTitle(), + "products.$productIndex" => __("The product :product is sold out", [ + 'id' => $productId, + 'product' => $product->getTitle(), ]), ]); } @@ -277,24 +277,24 @@ private function validateSoldOutTickets(int $ticketId, int $ticketIndex, TicketD /** * @throws ValidationException */ - private function validatePriceIdAndQuantity(int $ticketIndex, array $ticketAndQuantities, TicketDomainObject $ticket): void + private function validatePriceIdAndQuantity(int $productIndex, array $productAndQuantities, ProductDomainObject $product): void { $errors = []; - foreach ($ticketAndQuantities['quantities'] as $quantityIndex => $quantityData) { + foreach ($productAndQuantities['quantities'] as $quantityIndex => $quantityData) { $priceId = $quantityData['price_id'] ?? null; $quantity = $quantityData['quantity'] ?? null; if (null === $priceId || null === $quantity) { $missingField = null === $priceId ? 'price_id' : 'quantity'; - $errors["tickets.$ticketIndex.quantities.$quantityIndex.$missingField"] = __(":field must be specified", [ + $errors["products.$productIndex.quantities.$quantityIndex.$missingField"] = __(":field must be specified", [ 'field' => ucfirst($missingField) ]); } - $validPriceIds = $ticket->getTicketPrices()?->map(fn(TicketPriceDomainObject $price) => $price->getId()); + $validPriceIds = $product->getProductPrices()?->map(fn(ProductPriceDomainObject $price) => $price->getId()); if (!in_array($priceId, $validPriceIds->toArray(), true)) { - $errors["tickets.$ticketIndex.quantities.$quantityIndex.price_id"] = __('Invalid price ID'); + $errors["products.$productIndex.quantities.$quantityIndex.price_id"] = __('Invalid price ID'); } } @@ -306,32 +306,32 @@ private function validatePriceIdAndQuantity(int $ticketIndex, array $ticketAndQu /** * @throws ValidationException */ - private function validateTicketPricesQuantity(array $quantities, TicketDomainObject $ticket, int $ticketIndex): void + private function validateProductPricesQuantity(array $quantities, ProductDomainObject $product, int $productIndex): void { - foreach ($quantities as $ticketQuantity) { - $numberAvailable = $this->availableTicketQuantities - ->ticketQuantities - ->where('ticket_id', $ticket->getId()) - ->where('price_id', $ticketQuantity['price_id']) + foreach ($quantities as $productQuantity) { + $numberAvailable = $this->availableProductQuantities + ->productQuantities + ->where('product_id', $product->getId()) + ->where('price_id', $productQuantity['price_id']) ->first()?->quantity_available; - /** @var TicketPriceDomainObject $ticketPrice */ - $ticketPrice = $ticket->getTicketPrices() - ?->first(fn(TicketPriceDomainObject $price) => $price->getId() === $ticketQuantity['price_id']); + /** @var ProductPriceDomainObject $productPrice */ + $productPrice = $product->getProductPrices() + ?->first(fn(ProductPriceDomainObject $price) => $price->getId() === $productQuantity['price_id']); - if ($ticketQuantity['quantity'] > $numberAvailable) { + if ($productQuantity['quantity'] > $numberAvailable) { if ($numberAvailable === 0) { throw ValidationException::withMessages([ - "tickets.$ticketIndex" => __("The ticket :ticket is sold out", [ - 'ticket' => $ticket->getTitle() . ($ticketPrice->getLabel() ? ' - ' . $ticketPrice->getLabel() : ''), + "products.$productIndex" => __("The product :product is sold out", [ + 'product' => $product->getTitle() . ($productPrice->getLabel() ? ' - ' . $productPrice->getLabel() : ''), ]), ]); } throw ValidationException::withMessages([ - "tickets.$ticketIndex" => __("The maximum number of tickets available for :ticket is :max", [ + "products.$productIndex" => __("The maximum number of products available for :product is :max", [ 'max' => $numberAvailable, - 'ticket' => $ticket->getTitle() . ($ticketPrice->getLabel() ? ' - ' . $ticketPrice->getLabel() : ''), + 'product' => $product->getTitle() . ($productPrice->getLabel() ? ' - ' . $productPrice->getLabel() : ''), ]), ]); } @@ -343,34 +343,34 @@ private function validateTicketPricesQuantity(array $quantities, TicketDomainObj */ private function validateOverallCapacity(array $data): void { - foreach ($this->availableTicketQuantities->capacities as $capacity) { - if ($capacity->getTickets() === null) { + foreach ($this->availableProductQuantities->capacities as $capacity) { + if ($capacity->getProducts() === null) { continue; } - $ticketIds = $capacity->getTickets()->map(fn(TicketDomainObject $ticket) => $ticket->getId()); - $totalQuantity = collect($data['tickets']) - ->filter(fn($ticket) => in_array($ticket['ticket_id'], $ticketIds->toArray(), true)) - ->sum(fn($ticket) => collect($ticket['quantities'])->sum('quantity')); + $productIds = $capacity->getProducts()->map(fn(ProductDomainObject $product) => $product->getId()); + $totalQuantity = collect($data['products']) + ->filter(fn($product) => in_array($product['product_id'], $productIds->toArray(), true)) + ->sum(fn($product) => collect($product['quantities'])->sum('quantity')); - $reservedTicketQuantities = $capacity->getTickets() - ->map(fn(TicketDomainObject $ticket) => $this - ->availableTicketQuantities - ->ticketQuantities - ->where('ticket_id', $ticket->getId()) + $reservedProductQuantities = $capacity->getProducts() + ->map(fn(ProductDomainObject $product) => $this + ->availableProductQuantities + ->productQuantities + ->where('product_id', $product->getId()) ->sum('quantity_reserved') ) ->sum(); - if ($totalQuantity > ($capacity->getAvailableCapacity() - $reservedTicketQuantities)) { - if ($capacity->getAvailableCapacity() - $reservedTicketQuantities <= 0) { + if ($totalQuantity > ($capacity->getAvailableCapacity() - $reservedProductQuantities)) { + if ($capacity->getAvailableCapacity() - $reservedProductQuantities <= 0) { throw ValidationException::withMessages([ - 'tickets' => __('Sorry, these tickets are sold out'), + 'products' => __('Sorry, these products are sold out'), ]); } throw ValidationException::withMessages([ - 'tickets' => __('The maximum number of tickets available is :max', [ + 'products' => __('The maximum number of products available is :max', [ 'max' => $capacity->getAvailableCapacity(), ]), ]); diff --git a/backend/app/Services/Domain/Order/OrderItemProcessingService.php b/backend/app/Services/Domain/Order/OrderItemProcessingService.php index 3ab93a6e7b..3f7bf38af3 100644 --- a/backend/app/Services/Domain/Order/OrderItemProcessingService.php +++ b/backend/app/Services/Domain/Order/OrderItemProcessingService.php @@ -3,19 +3,19 @@ namespace HiEvents\Services\Domain\Order; use HiEvents\DomainObjects\EventDomainObject; -use HiEvents\DomainObjects\Generated\TicketDomainObjectAbstract; +use HiEvents\DomainObjects\Generated\ProductDomainObjectAbstract; use HiEvents\DomainObjects\OrderDomainObject; +use HiEvents\DomainObjects\ProductDomainObject; +use HiEvents\DomainObjects\ProductPriceDomainObject; use HiEvents\DomainObjects\PromoCodeDomainObject; use HiEvents\DomainObjects\TaxAndFeesDomainObject; -use HiEvents\DomainObjects\TicketDomainObject; -use HiEvents\DomainObjects\TicketPriceDomainObject; use HiEvents\Helper\Currency; use HiEvents\Repository\Interfaces\OrderRepositoryInterface; -use HiEvents\Repository\Interfaces\TicketRepositoryInterface; +use HiEvents\Repository\Interfaces\ProductRepositoryInterface; +use HiEvents\Services\Domain\Product\DTO\OrderProductPriceDTO; +use HiEvents\Services\Domain\Product\ProductPriceService; use HiEvents\Services\Domain\Tax\TaxAndFeeCalculationService; -use HiEvents\Services\Domain\Ticket\DTO\OrderTicketPriceDTO; -use HiEvents\Services\Domain\Ticket\TicketPriceService; -use HiEvents\Services\Handlers\Order\DTO\TicketOrderDetailsDTO; +use HiEvents\Services\Handlers\Order\DTO\ProductOrderDetailsDTO; use Illuminate\Support\Collection; use Symfony\Component\Routing\Exception\ResourceNotFoundException; @@ -23,49 +23,49 @@ { public function __construct( private OrderRepositoryInterface $orderRepository, - private TicketRepositoryInterface $ticketRepository, + private ProductRepositoryInterface $productRepository, private TaxAndFeeCalculationService $taxCalculationService, - private TicketPriceService $ticketPriceService, + private ProductPriceService $productPriceService, ) { } /** * @param OrderDomainObject $order - * @param Collection $ticketsOrderDetails + * @param Collection $productsOrderDetails * @param EventDomainObject $event * @param PromoCodeDomainObject|null $promoCode * @return Collection */ public function process( OrderDomainObject $order, - Collection $ticketsOrderDetails, + Collection $productsOrderDetails, EventDomainObject $event, ?PromoCodeDomainObject $promoCode ): Collection { $orderItems = collect(); - foreach ($ticketsOrderDetails as $ticketOrderDetail) { - $ticket = $this->ticketRepository + foreach ($productsOrderDetails as $productOrderDetail) { + $product = $this->productRepository ->loadRelation(TaxAndFeesDomainObject::class) - ->loadRelation(TicketPriceDomainObject::class) + ->loadRelation(ProductPriceDomainObject::class) ->findFirstWhere([ - TicketDomainObjectAbstract::ID => $ticketOrderDetail->ticket_id, - TicketDomainObjectAbstract::EVENT_ID => $event->getId(), + ProductDomainObjectAbstract::ID => $productOrderDetail->product_id, + ProductDomainObjectAbstract::EVENT_ID => $event->getId(), ]); - if ($ticket === null) { + if ($product === null) { throw new ResourceNotFoundException( - __('Ticket with id :id not found', ['id' => $ticketOrderDetail->ticket_id]) + __('Product with id :id not found', ['id' => $productOrderDetail->product_id]) ); } - $ticketOrderDetail->quantities->each(function (OrderTicketPriceDTO $ticketPrice) use ($promoCode, $order, $orderItems, $ticket) { - if ($ticketPrice->quantity === 0) { + $productOrderDetail->quantities->each(function (OrderProductPriceDTO $productPrice) use ($promoCode, $order, $orderItems, $product) { + if ($productPrice->quantity === 0) { return; } - $orderItemData = $this->calculateOrderItemData($ticket, $ticketPrice, $order, $promoCode); + $orderItemData = $this->calculateOrderItemData($product, $productPrice, $order, $promoCode); $orderItems->push($this->orderRepository->addOrderItem($orderItemData)); }); } @@ -74,33 +74,34 @@ public function process( } private function calculateOrderItemData( - TicketDomainObject $ticket, - OrderTicketPriceDTO $ticketPriceDetails, + ProductDomainObject $product, + OrderProductPriceDTO $productPriceDetails, OrderDomainObject $order, ?PromoCodeDomainObject $promoCode ): array { - $prices = $this->ticketPriceService->getPrice($ticket, $ticketPriceDetails, $promoCode); + $prices = $this->productPriceService->getPrice($product, $productPriceDetails, $promoCode); $priceWithDiscount = $prices->price; $priceBeforeDiscount = $prices->price_before_discount; - $itemTotalWithDiscount = $priceWithDiscount * $ticketPriceDetails->quantity; + $itemTotalWithDiscount = $priceWithDiscount * $productPriceDetails->quantity; - $taxesAndFees = $this->taxCalculationService->calculateTaxAndFeesForTicket( - ticket: $ticket, + $taxesAndFees = $this->taxCalculationService->calculateTaxAndFeesForProduct( + product: $product, price: $priceWithDiscount, - quantity: $ticketPriceDetails->quantity + quantity: $productPriceDetails->quantity ); return [ - 'ticket_id' => $ticket->getId(), - 'ticket_price_id' => $ticketPriceDetails->price_id, - 'quantity' => $ticketPriceDetails->quantity, + 'product_type' => $product->getProductType(), + 'product_id' => $product->getId(), + 'product_price_id' => $productPriceDetails->price_id, + 'quantity' => $productPriceDetails->quantity, 'price_before_discount' => $priceBeforeDiscount, 'total_before_additions' => Currency::round($itemTotalWithDiscount), 'price' => $priceWithDiscount, 'order_id' => $order->getId(), - 'item_name' => $this->getOrderItemLabel($ticket, $ticketPriceDetails->price_id), + 'item_name' => $this->getOrderItemLabel($product, $productPriceDetails->price_id), 'total_tax' => $taxesAndFees->taxTotal, 'total_service_fee' => $taxesAndFees->feeTotal, 'total_gross' => Currency::round($itemTotalWithDiscount + $taxesAndFees->taxTotal + $taxesAndFees->feeTotal), @@ -108,14 +109,14 @@ private function calculateOrderItemData( ]; } - private function getOrderItemLabel(TicketDomainObject $ticket, int $priceId): string + private function getOrderItemLabel(ProductDomainObject $product, int $priceId): string { - if ($ticket->isTieredType()) { - return $ticket->getTitle() . ' - ' . $ticket->getTicketPrices() + if ($product->isTieredType()) { + return $product->getTitle() . ' - ' . $product->getProductPrices() ?->filter(fn($p) => $p->getId() === $priceId)->first() ?->getLabel(); } - return $ticket->getTitle(); + return $product->getTitle(); } } diff --git a/backend/app/Services/Domain/Payment/Stripe/EventHandlers/PaymentIntentSucceededHandler.php b/backend/app/Services/Domain/Payment/Stripe/EventHandlers/PaymentIntentSucceededHandler.php index 8f7f37b29c..11bc97e98c 100644 --- a/backend/app/Services/Domain/Payment/Stripe/EventHandlers/PaymentIntentSucceededHandler.php +++ b/backend/app/Services/Domain/Payment/Stripe/EventHandlers/PaymentIntentSucceededHandler.php @@ -19,7 +19,7 @@ use HiEvents\Repository\Eloquent\Value\Relationship; use HiEvents\Repository\Interfaces\OrderRepositoryInterface; use HiEvents\Services\Domain\Payment\Stripe\StripeRefundExpiredOrderService; -use HiEvents\Services\Domain\Ticket\TicketQuantityUpdateService; +use HiEvents\Services\Domain\Product\ProductQuantityUpdateService; use Illuminate\Database\DatabaseManager; use Stripe\Exception\ApiErrorException; use Stripe\PaymentIntent; @@ -30,7 +30,7 @@ public function __construct( private OrderRepositoryInterface $orderRepository, private StripePaymentsRepository $stripePaymentsRepository, - private TicketQuantityUpdateService $quantityUpdateService, + private ProductQuantityUpdateService $quantityUpdateService, private StripeRefundExpiredOrderService $refundExpiredOrderService, private DatabaseManager $databaseManager, ) @@ -93,8 +93,8 @@ private function updateStripePaymentInfo(PaymentIntent $paymentIntent, StripePay /** * If the order has expired (reserved_until is in the past), refund the payment and throw an exception. - * This does seem quite extreme, but it ensures we don't oversell tickets. As far as I can see - * this is how Ticketmaster and other ticketing systems work. + * This does seem quite extreme, but it ensures we don't oversell products. As far as I can see + * this is how Productmaster and other producting systems work. * * @throws ApiErrorException * @throws RoundingNecessaryException @@ -102,7 +102,7 @@ private function updateStripePaymentInfo(PaymentIntent $paymentIntent, StripePay * @throws MathException * @throws UnknownCurrencyException * @throws NumberFormatException - * @todo We could check to see if there are tickets available, and if so, complete the order. + * @todo We could check to see if there are products available, and if so, complete the order. * This would be a better user experience. * */ diff --git a/backend/app/Services/Domain/Product/AvailableProductQuantitiesFetchService.php b/backend/app/Services/Domain/Product/AvailableProductQuantitiesFetchService.php new file mode 100644 index 0000000000..12e060e2dc --- /dev/null +++ b/backend/app/Services/Domain/Product/AvailableProductQuantitiesFetchService.php @@ -0,0 +1,173 @@ +config->get('app.homepage_product_quantities_cache_ttl')) { + $cachedData = $this->getDataFromCache($eventId); + if ($cachedData) { + return $cachedData; + } + } + + $capacities = $this->capacityAssignmentRepository + ->loadRelation(ProductDomainObject::class) + ->findWhere([ + 'event_id' => $eventId, + 'applies_to' => CapacityAssignmentAppliesTo::PRODUCTS->name, + 'status' => CapacityAssignmentStatus::ACTIVE->name, + ]); + + $reservedProductQuantities = $this->fetchReservedProductQuantities($eventId); + $productCapacities = $this->calculateProductCapacities($capacities); + + $quantities = $reservedProductQuantities->map(function (AvailableProductQuantitiesDTO $dto) use ($productCapacities) { + $productId = $dto->product_id; + if (isset($productCapacities[$productId])) { + $dto->quantity_available = min(array_merge([$dto->quantity_available], $productCapacities[$productId]->map->getAvailableCapacity()->toArray())); + $dto->capacities = $productCapacities[$productId]; + } + + return $dto; + }); + + $finalData = new AvailableProductQuantitiesResponseDTO( + productQuantities: $quantities, + capacities: $capacities + ); + + if (!$ignoreCache && $this->config->get('app.homepage_product_quantities_cache_ttl')) { + $this->cache->put($this->getCacheKey($eventId), $finalData, $this->config->get('app.homepage_product_quantities_cache_ttl')); + } + + return $finalData; + } + + private function fetchReservedProductQuantities(int $eventId): Collection + { + $result = $this->db->select(<< NOW() + AND orders.deleted_at IS NULL + THEN order_items.quantity + ELSE 0 + END + ) AS quantity_reserved + FROM products + JOIN product_prices ON products.id = product_prices.product_id + LEFT JOIN order_items ON order_items.product_id = products.id + AND order_items.product_price_id = product_prices.id + LEFT JOIN orders ON orders.id = order_items.order_id + AND orders.event_id = products.event_id + AND orders.deleted_at IS NULL + WHERE + products.event_id = :eventId + AND products.deleted_at IS NULL + AND product_prices.deleted_at IS NULL + GROUP BY products.id, product_prices.id + ) + SELECT + products.id AS product_id, + product_prices.id AS product_price_id, + products.title AS product_title, + product_prices.label AS price_label, + product_prices.initial_quantity_available, + product_prices.quantity_sold, + COALESCE( + product_prices.initial_quantity_available + - product_prices.quantity_sold + - COALESCE(reserved_quantities.quantity_reserved, 0), + 0) AS quantity_available, + COALESCE(reserved_quantities.quantity_reserved, 0) AS quantity_reserved, + CASE WHEN product_prices.initial_quantity_available IS NULL + THEN TRUE + ELSE FALSE + END AS unlimited_quantity_available + FROM products + JOIN product_prices ON products.id = product_prices.product_id + LEFT JOIN reserved_quantities ON products.id = reserved_quantities.product_id + AND product_prices.id = reserved_quantities.product_price_id + WHERE + products.event_id = :eventId + AND products.deleted_at IS NULL + AND product_prices.deleted_at IS NULL + GROUP BY products.id, product_prices.id, reserved_quantities.quantity_reserved; + SQL, [ + 'eventId' => $eventId, + 'reserved' => OrderStatus::RESERVED->name + ]); + + return collect($result)->map(fn($row) => AvailableProductQuantitiesDTO::fromArray([ + 'product_id' => $row->product_id, + 'price_id' => $row->product_price_id, + 'product_title' => $row->product_title, + 'price_label' => $row->price_label, + 'quantity_available' => $row->unlimited_quantity_available ? Constants::INFINITE : $row->quantity_available, + 'initial_quantity_available' => $row->initial_quantity_available, + 'quantity_reserved' => $row->quantity_reserved, + 'capacities' => new Collection(), + ])); + } + + /** + * @param Collection $capacities + */ + private function calculateProductCapacities(Collection $capacities): array + { + $productCapacities = []; + foreach ($capacities as $capacity) { + foreach ($capacity->getProducts() as $product) { + $productId = $product->getId(); + if (!isset($productCapacities[$productId])) { + $productCapacities[$productId] = collect(); + } + + $productCapacities[$productId]->push($capacity); + } + } + + return $productCapacities; + } + + private function getDataFromCache(int $eventId): ?AvailableProductQuantitiesResponseDTO + { + return $this->cache->get($this->getCacheKey($eventId)); + } + + private function getCacheKey(int $eventId): string + { + return "event.$eventId.available_product_quantities"; + } +} diff --git a/backend/app/Services/Domain/Product/CreateProductService.php b/backend/app/Services/Domain/Product/CreateProductService.php new file mode 100644 index 0000000000..22671398fb --- /dev/null +++ b/backend/app/Services/Domain/Product/CreateProductService.php @@ -0,0 +1,124 @@ +databaseManager->transaction(function () use ($accountId, $taxAndFeeIds, $product) { + $persistedProduct = $this->persistProduct($product); + + if ($taxAndFeeIds) { + $this->associateTaxesAndFees($persistedProduct, $taxAndFeeIds, $accountId); + } + + return $this->createProductPrices($persistedProduct, $product); + }); + } + + private function persistProduct(ProductDomainObject $productsData): ProductDomainObject + { + $event = $this->eventRepository->findById($productsData->getEventId()); + + return $this->productRepository->create([ + 'title' => $productsData->getTitle(), + 'type' => $productsData->getType(), + 'product_type' => $productsData->getProductType(), + 'order' => $this->productOrderingService->getOrderForNewProduct( + eventId: $productsData->getEventId(), + productCategoryId: $productsData->getProductCategoryId(), + ), + 'sale_start_date' => $productsData->getSaleStartDate() + ? DateHelper::convertToUTC($productsData->getSaleStartDate(), $event->getTimezone()) + : null, + 'sale_end_date' => $productsData->getSaleEndDate() + ? DateHelper::convertToUTC($productsData->getSaleEndDate(), $event->getTimezone()) + : null, + 'max_per_order' => $productsData->getMaxPerOrder(), + 'description' => $this->purifier->purify($productsData->getDescription()), + 'start_collapsed' => $productsData->getStartCollapsed(), + 'min_per_order' => $productsData->getMinPerOrder(), + 'is_hidden' => $productsData->getIsHidden(), + 'hide_before_sale_start_date' => $productsData->getHideBeforeSaleStartDate(), + 'hide_after_sale_end_date' => $productsData->getHideAfterSaleEndDate(), + 'hide_when_sold_out' => $productsData->getHideWhenSoldOut(), + 'show_quantity_remaining' => $productsData->getShowQuantityRemaining(), + 'is_hidden_without_promo_code' => $productsData->getIsHiddenWithoutPromoCode(), + 'event_id' => $productsData->getEventId(), + 'product_category_id' => $productsData->getProductCategoryId(), + ]); + } + + /** + * @throws Exception + */ + private function createProductTaxesAndFees( + ProductDomainObject $product, + array $taxAndFeeIds, + int $accountId, + ): Collection + { + return $this->taxAndProductAssociationService->addTaxesToProduct( + new TaxAndProductAssociateParams( + productId: $product->getId(), + accountId: $accountId, + taxAndFeeIds: $taxAndFeeIds, + ), + ); + } + + /** + * @throws Exception + */ + private function associateTaxesAndFees(ProductDomainObject $persistedProduct, array $taxAndFeeIds, int $accountId): void + { + $persistedProduct->setTaxAndFees($this->createProductTaxesAndFees( + product: $persistedProduct, + taxAndFeeIds: $taxAndFeeIds, + accountId: $accountId, + )); + } + + private function createProductPrices(ProductDomainObject $persistedProduct, ProductDomainObject $product): ProductDomainObject + { + $prices = $this->priceCreateService->createPrices( + productId: $persistedProduct->getId(), + prices: $product->getProductPrices(), + event: $this->eventRepository->findById($product->getEventId()), + ); + + return $persistedProduct->setProductPrices($prices); + } +} diff --git a/backend/app/Services/Domain/Ticket/DTO/AvailableTicketQuantitiesDTO.php b/backend/app/Services/Domain/Product/DTO/AvailableProductQuantitiesDTO.php similarity index 74% rename from backend/app/Services/Domain/Ticket/DTO/AvailableTicketQuantitiesDTO.php rename to backend/app/Services/Domain/Product/DTO/AvailableProductQuantitiesDTO.php index 72c032b6b1..01c68affaf 100644 --- a/backend/app/Services/Domain/Ticket/DTO/AvailableTicketQuantitiesDTO.php +++ b/backend/app/Services/Domain/Product/DTO/AvailableProductQuantitiesDTO.php @@ -1,17 +1,17 @@ */ - public Collection $ticketQuantities, + /** @var Collection */ + public Collection $productQuantities, /** @var Collection */ public ?Collection $capacities = null, ) diff --git a/backend/app/Services/Domain/Product/DTO/CreateProductDTO.php b/backend/app/Services/Domain/Product/DTO/CreateProductDTO.php new file mode 100644 index 0000000000..3784ab8573 --- /dev/null +++ b/backend/app/Services/Domain/Product/DTO/CreateProductDTO.php @@ -0,0 +1,10 @@ +databaseManager->transaction(function () use ($productId, $eventId) { + if ($this->productRepository->hasAssociatedOrders($productId)) { + throw new CannotDeleteEntityException( + __('You cannot delete this product because it has orders associated with it. You can hide it instead.') + ); + } + + $this->productRepository->deleteWhere( + [ + ProductDomainObjectAbstract::EVENT_ID => $eventId, + ProductDomainObjectAbstract::ID => $productId, + ] + ); + + $this->productPriceRepository->deleteWhere( + [ + ProductPriceDomainObjectAbstract::PRODUCT_ID => $productId, + ] + ); + }); + + $this->logger->info( + sprintf('Product with id %d was deleted from event with id %d', $productId, $eventId), + [ + 'product_id' => $productId, + 'event_id' => $eventId, + ] + ); + } +} diff --git a/backend/app/Services/Domain/Product/EventProductValidationService.php b/backend/app/Services/Domain/Product/EventProductValidationService.php new file mode 100644 index 0000000000..ae46396bda --- /dev/null +++ b/backend/app/Services/Domain/Product/EventProductValidationService.php @@ -0,0 +1,35 @@ +productRepository->findWhere([ + 'event_id' => $eventId, + ])->map(fn(ProductDomainObject $product) => $product->getId()) + ->toArray(); + + $invalidProductIds = array_diff($productIds, $validProductIds); + + if (!empty($invalidProductIds)) { + throw new UnrecognizedProductIdException( + __('Invalid product ids: :ids', ['ids' => implode(', ', $invalidProductIds)]) + ); + } + } +} diff --git a/backend/app/Services/Domain/Product/Exception/UnrecognizedProductIdException.php b/backend/app/Services/Domain/Product/Exception/UnrecognizedProductIdException.php new file mode 100644 index 0000000000..f788e3fe82 --- /dev/null +++ b/backend/app/Services/Domain/Product/Exception/UnrecognizedProductIdException.php @@ -0,0 +1,10 @@ + $productsCategories + * @param PromoCodeDomainObject|null $promoCode + * @param bool $hideSoldOutProducts + * @return Collection + */ + public function filter( + Collection $productsCategories, + ?PromoCodeDomainObject $promoCode = null, + bool $hideSoldOutProducts = true, + ): Collection + { + if ($productsCategories->isEmpty()) { + return $productsCategories; + } + + $products = $productsCategories + ->flatMap(fn(ProductCategoryDomainObject $category) => $category->getProducts()); + + if ($products->isEmpty()) { + return $productsCategories; + } + + $productQuantities = $this + ->fetchAvailableProductQuantitiesService + ->getAvailableProductQuantities($products->first()->getEventId()); + + $filteredProducts = $products + ->map(fn(ProductDomainObject $product) => $this->processProduct($product, $productQuantities->productQuantities, $promoCode)) + ->reject(fn(ProductDomainObject $product) => $this->filterProduct($product, $promoCode, $hideSoldOutProducts)) + ->each(fn(ProductDomainObject $product) => $this->processProductPrices($product, $hideSoldOutProducts)); + + return $productsCategories + ->map(fn(ProductCategoryDomainObject $category) => $category->setProducts( + $filteredProducts->where( + static fn(ProductDomainObject $product) => $product->getProductCategoryId() === $category->getId() + ) + )); + } + + private function isHiddenByPromoCode(ProductDomainObject $product, ?PromoCodeDomainObject $promoCode): bool + { + return $product->getIsHiddenWithoutPromoCode() && !( + $promoCode + && $promoCode->appliesToProduct($product) + ); + } + + private function shouldProductBeDiscounted(?PromoCodeDomainObject $promoCode, ProductDomainObject $product): bool + { + if ($product->isDonationType() || $product->isFreeType()) { + return false; + } + + return $promoCode + && $promoCode->isDiscountCode() + && $promoCode->appliesToProduct($product); + } + + /** + * @param PromoCodeDomainObject|null $promoCode + * @param ProductDomainObject $product + * @param Collection $productQuantities + * @return ProductDomainObject + */ + private function processProduct( + ProductDomainObject $product, + Collection $productQuantities, + ?PromoCodeDomainObject $promoCode = null, + ): ProductDomainObject + { + if ($this->shouldProductBeDiscounted($promoCode, $product)) { + $product->getProductPrices()?->each(function (ProductPriceDomainObject $price) use ($product, $promoCode) { + $price->setPriceBeforeDiscount($price->getPrice()); + $price->setPrice($this->productPriceService->getIndividualPrice($product, $price, $promoCode)); + }); + } + + $product->getProductPrices()?->map(function (ProductPriceDomainObject $price) use ($productQuantities) { + $availableQuantity = $productQuantities->where('price_id', $price->getId())->first()?->quantity_available; + $availableQuantity = $availableQuantity === Constants::INFINITE ? null : $availableQuantity; + $price->setQuantityAvailable( + max($availableQuantity, 0) + ); + }); + + // If there is a capacity assigned to the product, we set the capacity to capacity available qty, or the sum of all + // product prices qty, whichever is lower + $productQuantities->each(function (AvailableProductQuantitiesDTO $quantity) use ($product) { + if ($quantity->capacities !== null && $quantity->capacities->isNotEmpty() && $quantity->product_id === $product->getId()) { + $product->setQuantityAvailable( + $quantity->capacities->min(fn(CapacityAssignmentDomainObject $capacity) => $capacity->getAvailableCapacity()) + ); + } + }); + + return $product; + } + + private function filterProduct( + ProductDomainObject $product, + ?PromoCodeDomainObject $promoCode = null, + bool $hideSoldOutProducts = true, + ): bool + { + $hidden = false; + + if ($this->isHiddenByPromoCode($product, $promoCode)) { + $product->setOffSaleReason(__('Product is hidden without promo code')); + $hidden = true; + } + + if ($product->isSoldOut() && $product->getHideWhenSoldOut()) { + $product->setOffSaleReason(__('Product is sold out')); + $hidden = true; + } + + if ($product->isBeforeSaleStartDate() && $product->getHideBeforeSaleStartDate()) { + $product->setOffSaleReason(__('Product is before sale start date')); + $hidden = true; + } + + if ($product->isAfterSaleEndDate() && $product->getHideAfterSaleEndDate()) { + $product->setOffSaleReason(__('Product is after sale end date')); + $hidden = true; + } + + if ($product->getIsHidden()) { + $product->setOffSaleReason(__('Product is hidden')); + $hidden = true; + } + + return $hidden && $hideSoldOutProducts; + } + + private function processProductPrice(ProductDomainObject $product, ProductPriceDomainObject $price): void + { + $taxAndFees = $this->taxCalculationService + ->calculateTaxAndFeesForProductPrice($product, $price); + + $price + ->setTaxTotal(Currency::round($taxAndFees->taxTotal)) + ->setFeeTotal(Currency::round($taxAndFees->feeTotal)); + + $price->setIsAvailable($this->getPriceAvailability($price, $product)); + } + + private function filterProductPrice( + ProductDomainObject $product, + ProductPriceDomainObject $price, + bool $hideSoldOutProducts = true + ): bool + { + $hidden = false; + + if (!$product->isTieredType()) { + return false; + } + + if ($price->isBeforeSaleStartDate() && $product->getHideBeforeSaleStartDate()) { + $price->setOffSaleReason(__('Price is before sale start date')); + $hidden = true; + } + + if ($price->isAfterSaleEndDate() && $product->getHideAfterSaleEndDate()) { + $price->setOffSaleReason(__('Price is after sale end date')); + $hidden = true; + } + + if ($price->isSoldOut() && $product->getHideWhenSoldOut()) { + $price->setOffSaleReason(__('Price is sold out')); + $hidden = true; + } + + if ($price->getIsHidden()) { + $price->setOffSaleReason(__('Price is hidden')); + $hidden = true; + } + + return $hidden && $hideSoldOutProducts; + } + + private function processProductPrices(ProductDomainObject $product, bool $hideSoldOutProducts = true): void + { + $product->setProductPrices( + $product->getProductPrices() + ?->each(fn(ProductPriceDomainObject $price) => $this->processProductPrice($product, $price)) + ->reject(fn(ProductPriceDomainObject $price) => $this->filterProductPrice($product, $price, $hideSoldOutProducts)) + ); + } + + /** + * For non-tiered products, we can inherit the availability of the product. + * + * @param ProductPriceDomainObject $price + * @param ProductDomainObject $product + * @return bool + */ + private function getPriceAvailability(ProductPriceDomainObject $price, ProductDomainObject $product): bool + { + if ($product->isTieredType()) { + return !$price->isSoldOut() + && !$price->isBeforeSaleStartDate() + && !$price->isAfterSaleEndDate() + && !$price->getIsHidden(); + } + + return !$product->isSoldOut() + && !$product->isBeforeSaleStartDate() + && !$product->isAfterSaleEndDate() + && !$product->getIsHidden(); + } +} diff --git a/backend/app/Services/Domain/Product/ProductOrderingService.php b/backend/app/Services/Domain/Product/ProductOrderingService.php new file mode 100644 index 0000000000..11e9498ae5 --- /dev/null +++ b/backend/app/Services/Domain/Product/ProductOrderingService.php @@ -0,0 +1,24 @@ +productRepository->findWhere([ + 'event_id' => $eventId, + 'product_category_id' => $productCategoryId, + ]) + ->max((static fn(ProductDomainObject $product) => $product->getOrder())) ?? 0) + 1; + } +} diff --git a/backend/app/Services/Domain/Ticket/TicketPriceCreateService.php b/backend/app/Services/Domain/Product/ProductPriceCreateService.php similarity index 66% rename from backend/app/Services/Domain/Ticket/TicketPriceCreateService.php rename to backend/app/Services/Domain/Product/ProductPriceCreateService.php index bff6de6dc7..57fae4a659 100644 --- a/backend/app/Services/Domain/Ticket/TicketPriceCreateService.php +++ b/backend/app/Services/Domain/Product/ProductPriceCreateService.php @@ -1,29 +1,29 @@ map(fn(TicketPriceDomainObject $price, int $index) => $this->ticketPriceRepository->create([ - 'ticket_id' => $ticketId, + return (new Collection($prices->map(fn(ProductPriceDomainObject $price, int $index) => $this->productPriceRepository->create([ + 'product_id' => $productId, 'price' => $price->getPrice(), 'label' => $price->getLabel(), 'sale_start_date' => $price->getSaleStartDate() diff --git a/backend/app/Services/Domain/Product/ProductPriceService.php b/backend/app/Services/Domain/Product/ProductPriceService.php new file mode 100644 index 0000000000..aa29d65503 --- /dev/null +++ b/backend/app/Services/Domain/Product/ProductPriceService.php @@ -0,0 +1,77 @@ +getPrice($product, new OrderProductPriceDTO( + quantity: 1, + price_id: $price->getId(), + ), $promoCode)->price; + } + + public function getPrice( + ProductDomainObject $product, + OrderProductPriceDTO $productOrderDetail, + ?PromoCodeDomainObject $promoCode + ): PriceDTO + { + $price = $this->determineProductPrice($product, $productOrderDetail); + + if ($product->getType() === ProductPriceType::FREE->name) { + return new PriceDTO(0.00); + } + + if ($product->getType() === ProductPriceType::DONATION->name) { + return new PriceDTO($price); + } + + if (!$promoCode || !$promoCode->appliesToProduct($product)) { + return new PriceDTO($price); + } + + if ($promoCode->getDiscountType() === PromoCodeDiscountTypeEnum::NONE->name) { + return new PriceDTO($price); + } + + if ($promoCode->isFixedDiscount()) { + $discountPrice = Currency::round($price - $promoCode->getDiscount()); + } elseif ($promoCode->isPercentageDiscount()) { + $discountPrice = Currency::round( + $price - ($price * ($promoCode->getDiscount() / 100)) + ); + } else { + $discountPrice = $price; + } + + return new PriceDTO( + price: max(0, $discountPrice), + price_before_discount: $price + ); + } + + private function determineProductPrice(ProductDomainObject $product, OrderProductPriceDTO $productOrderDetails): float + { + return match ($product->getType()) { + ProductPriceType::DONATION->name => max($product->getPrice(), $productOrderDetails->price), + ProductPriceType::PAID->name => $product->getPrice(), + ProductPriceType::FREE->name => 0.00, + ProductPriceType::TIERED->name => $product->getPriceById($productOrderDetails->price_id)?->getPrice() + }; + } +} diff --git a/backend/app/Services/Domain/Ticket/TicketPriceUpdateService.php b/backend/app/Services/Domain/Product/ProductPriceUpdateService.php similarity index 62% rename from backend/app/Services/Domain/Ticket/TicketPriceUpdateService.php rename to backend/app/Services/Domain/Product/ProductPriceUpdateService.php index f2bef504e6..02bcc08a4d 100644 --- a/backend/app/Services/Domain/Ticket/TicketPriceUpdateService.php +++ b/backend/app/Services/Domain/Product/ProductPriceUpdateService.php @@ -1,22 +1,22 @@ $existingPrices */ - Collection $existingPrices, - EventDomainObject $event, + ProductDomainObject $product, + UpsertProductDTO $productsData, + /** @var Collection $existingPrices */ + Collection $existingPrices, + EventDomainObject $event, ): void { - if ($ticketsData->type !== TicketType::TIERED) { - $prices = new Collection([new TicketPriceDTO( - price: $ticketsData->type === TicketType::FREE ? 0.00 : $ticketsData->prices->first()->price, + if ($productsData->type !== ProductPriceType::TIERED) { + $prices = new Collection([new ProductPriceDTO( + price: $productsData->type === ProductPriceType::FREE ? 0.00 : $productsData->prices->first()->price, label: null, sale_start_date: null, sale_end_date: null, - initial_quantity_available: $ticketsData->prices->first()->initial_quantity_available, + initial_quantity_available: $productsData->prices->first()->initial_quantity_available, id: $existingPrices->first()->getId(), )]); } else { - $prices = $ticketsData->prices; + $prices = $productsData->prices; } $order = 1; foreach ($prices as $price) { if ($price->id === null) { - $this->ticketPriceRepository->create([ - 'ticket_id' => $ticket->getId(), + $this->productPriceRepository->create([ + 'product_id' => $product->getId(), 'price' => $price->price, 'label' => $price->label, 'sale_start_date' => $price->sale_start_date @@ -64,8 +64,8 @@ public function updatePrices( 'order' => $order++, ]); } else { - $this->ticketPriceRepository->updateWhere([ - 'ticket_id' => $ticket->getId(), + $this->productPriceRepository->updateWhere([ + 'product_id' => $product->getId(), 'price' => $price->price, 'label' => $price->label, 'sale_start_date' => $price->sale_start_date @@ -93,16 +93,16 @@ private function deletePrices(?Collection $prices, Collection $existingPrices): { $pricesIds = $prices?->map(fn($price) => $price->id)->toArray(); - $existingPrices->each(function (TicketPriceDomainObject $price) use ($pricesIds) { + $existingPrices->each(function (ProductPriceDomainObject $price) use ($pricesIds) { if (in_array($price->getId(), $pricesIds)) { return; } if ($price->getQuantitySold() > 0) { throw new CannotDeleteEntityException( - __('Cannot delete ticket price with id :id because it has sales', ['id' => $price->getId()]) + __('Cannot delete product price with id :id because it has sales', ['id' => $price->getId()]) ); } - $this->ticketPriceRepository->deleteById($price->getId()); + $this->productPriceRepository->deleteById($price->getId()); }); } } diff --git a/backend/app/Services/Domain/Ticket/TicketQuantityUpdateService.php b/backend/app/Services/Domain/Product/ProductQuantityUpdateService.php similarity index 80% rename from backend/app/Services/Domain/Ticket/TicketQuantityUpdateService.php rename to backend/app/Services/Domain/Product/ProductQuantityUpdateService.php index edfe240298..3dc6a09fae 100644 --- a/backend/app/Services/Domain/Ticket/TicketQuantityUpdateService.php +++ b/backend/app/Services/Domain/Product/ProductQuantityUpdateService.php @@ -1,24 +1,24 @@ increaseCapacityAssignmentUsedCapacity($capacityAssignment->getId(), $adjustment); }); - $this->ticketPriceRepository->updateWhere([ + $this->productPriceRepository->updateWhere([ 'quantity_sold' => DB::raw('quantity_sold + ' . $adjustment), ], [ 'id' => $priceId, @@ -51,7 +51,7 @@ public function decreaseQuantitySold(int $priceId, int $adjustment = 1): void $this->decreaseCapacityAssignmentUsedCapacity($capacityAssignment->getId(), $adjustment); }); - $this->ticketPriceRepository->updateWhere([ + $this->productPriceRepository->updateWhere([ 'quantity_sold' => DB::raw('quantity_sold - ' . $adjustment), ], [ 'id' => $priceId, @@ -69,7 +69,7 @@ public function updateQuantitiesFromOrder(OrderDomainObject $order): void throw new InvalidArgumentException(__('Order has no order items')); } - $this->updateTicketQuantities($order); + $this->updateProductQuantities($order); }); } @@ -77,11 +77,11 @@ public function updateQuantitiesFromOrder(OrderDomainObject $order): void * @param OrderDomainObject $order * @return void */ - private function updateTicketQuantities(OrderDomainObject $order): void + private function updateProductQuantities(OrderDomainObject $order): void { /** @var OrderItemDomainObject $orderItem */ foreach ($order->getOrderItems() as $orderItem) { - $this->increaseQuantitySold($orderItem->getTicketPriceId(), $orderItem->getQuantity()); + $this->increaseQuantitySold($orderItem->getProductPriceId(), $orderItem->getQuantity()); } } @@ -109,10 +109,10 @@ private function decreaseCapacityAssignmentUsedCapacity(int $capacityAssignmentI */ private function getCapacityAssignments(int $priceId): Collection { - $price = $this->ticketPriceRepository->findFirstWhere([ + $price = $this->productPriceRepository->findFirstWhere([ 'id' => $priceId, ]); - return $this->ticketRepository->getCapacityAssignmentsByTicketId($price->getTicketId()); + return $this->productRepository->getCapacityAssignmentsByProductId($price->getProductId()); } } diff --git a/backend/app/Services/Domain/ProductCategory/CreateProductCategoryService.php b/backend/app/Services/Domain/ProductCategory/CreateProductCategoryService.php new file mode 100644 index 0000000000..dcd4969da9 --- /dev/null +++ b/backend/app/Services/Domain/ProductCategory/CreateProductCategoryService.php @@ -0,0 +1,33 @@ +productCategoryRepository->create([ + 'name' => $name, + 'description' => $description, + 'is_hidden' => $isHidden, + 'event_id' => $eventId, + 'order' => $this->productCategoryRepository->getNextOrder($eventId), + 'no_products_message' => $noProductsMessage, + ]); + } +} diff --git a/backend/app/Services/Domain/ProductCategory/DeleteProductCategoryService.php b/backend/app/Services/Domain/ProductCategory/DeleteProductCategoryService.php new file mode 100644 index 0000000000..b5fd74b205 --- /dev/null +++ b/backend/app/Services/Domain/ProductCategory/DeleteProductCategoryService.php @@ -0,0 +1,116 @@ +databaseManager->transaction(function () use ($productCategoryId, $eventId) { + $this->handleDeletion($productCategoryId, $eventId); + }); + } + + /** + * @throws Throwable + * @throws CannotDeleteEntityException + */ + private function handleDeletion(int $productCategoryId, int $eventId): void + { + $this->validateCanDeleteProductCategory($eventId); + + $this->deleteCategoryProducts($productCategoryId, $eventId); + + $this->deleteCategory($productCategoryId, $eventId); + } + + /** + * @throws CannotDeleteEntityException + * @throws Throwable + */ + private function deleteCategoryProducts(int $productCategoryId, int $eventId): void + { + $productsToDelete = $this->productRepository->findWhere( + [ + ProductCategoryDomainObjectAbstract::ID => $productCategoryId, + ProductCategoryDomainObjectAbstract::EVENT_ID => $eventId, + ] + ); + + $productsWhichCanNotBeDeleted = new Collection(); + + foreach ($productsToDelete as $product) { + try { + $this->deleteProductService->deleteProduct($product->getId(), $eventId); + } catch (CannotDeleteEntityException) { + $productsWhichCanNotBeDeleted->push($product); + } + } + + if ($productsWhichCanNotBeDeleted->isNotEmpty()) { + throw new CannotDeleteEntityException( + __('You cannot delete this product category because it contains the following products: :products. These products are linked to existing orders. Please move the :product_name to another category before attempting to delete this one.', [ + 'products' => $productsWhichCanNotBeDeleted->map(fn($product) => $product->getTitle())->implode(', '), + 'product_name' => $productsWhichCanNotBeDeleted->count() > 1 ? __('products') : __('product'), + ]) + ); + } + } + + private function deleteCategory(int $productCategoryId, int $eventId): void + { + $this->productCategoryRepository->deleteWhere( + [ + ProductCategoryDomainObjectAbstract::ID => $productCategoryId, + ProductCategoryDomainObjectAbstract::EVENT_ID => $eventId, + ] + ); + + $this->logger->info(__('Product category :productCategoryId has been deleted.', [ + 'product_category_id' => $productCategoryId, + 'event_id' => $eventId, + ])); + } + + /** + * @throws CannotDeleteEntityException + */ + private function validateCanDeleteProductCategory(int $eventId): void + { + $existingRelatedCategories = $this->productCategoryRepository->findWhere( + [ + ProductCategoryDomainObjectAbstract::EVENT_ID => $eventId, + ] + ); + + if ($existingRelatedCategories->count() === 1) { + throw new CannotDeleteEntityException( + __('You cannot delete the last product category. Please create another category before deleting this one.') + ); + } + } +} diff --git a/backend/app/Services/Domain/ProductCategory/GetProductCategoryService.php b/backend/app/Services/Domain/ProductCategory/GetProductCategoryService.php new file mode 100644 index 0000000000..ae03e4dd81 --- /dev/null +++ b/backend/app/Services/Domain/ProductCategory/GetProductCategoryService.php @@ -0,0 +1,47 @@ +productCategoryRepository + ->loadRelation(new Relationship( + domainObject: ProductDomainObject::class, + orderAndDirections: [ + new OrderAndDirection( + order: ProductCategoryDomainObjectAbstract::ORDER, + ), + ], + )) + ->findFirstWhere( + where: [ + 'id' => $categoryId, + 'event_id' => $eventId, + ] + ); + + if (!$category) { + throw new ResourceNotFoundException( + __('The product category with ID :id was not found.', ['id' => $categoryId]) + ); + } + + return $category; + } +} diff --git a/backend/app/Services/Domain/PromoCode/CreatePromoCodeService.php b/backend/app/Services/Domain/PromoCode/CreatePromoCodeService.php index 9e5609ce49..d8cdaa5ec5 100644 --- a/backend/app/Services/Domain/PromoCode/CreatePromoCodeService.php +++ b/backend/app/Services/Domain/PromoCode/CreatePromoCodeService.php @@ -9,30 +9,30 @@ use HiEvents\Helper\DateHelper; use HiEvents\Repository\Interfaces\EventRepositoryInterface; use HiEvents\Repository\Interfaces\PromoCodeRepositoryInterface; -use HiEvents\Services\Domain\Ticket\EventTicketValidationService; -use HiEvents\Services\Domain\Ticket\Exception\UnrecognizedTicketIdException; +use HiEvents\Services\Domain\Product\EventProductValidationService; +use HiEvents\Services\Domain\Product\Exception\UnrecognizedProductIdException; class CreatePromoCodeService { public function __construct( - private readonly PromoCodeRepositoryInterface $promoCodeRepository, - private readonly EventTicketValidationService $eventTicketValidationService, - private readonly EventRepositoryInterface $eventRepository, + private readonly PromoCodeRepositoryInterface $promoCodeRepository, + private readonly EventProductValidationService $eventProductValidationService, + private readonly EventRepositoryInterface $eventRepository, ) { } /** * @throws ResourceConflictException - * @throws UnrecognizedTicketIdException + * @throws UnrecognizedProductIdException */ public function createPromoCode(PromoCodeDomainObject $promoCode): PromoCodeDomainObject { $this->checkForDuplicateCode($promoCode); - if (!empty($promoCode->getApplicableTicketIds())) { - $this->eventTicketValidationService->validateTicketIds( - ticketIds: $promoCode->getApplicableTicketIds(), + if (!empty($promoCode->getApplicableProductIds())) { + $this->eventProductValidationService->validateProductIds( + productIds: $promoCode->getApplicableProductIds(), eventId: $promoCode->getEventId() ); } @@ -50,7 +50,7 @@ public function createPromoCode(PromoCodeDomainObject $promoCode): PromoCodeDoma ? DateHelper::convertToUTC($promoCode->getExpiryDate(), $event->getTimezone()) : null, PromoCodeDomainObjectAbstract::MAX_ALLOWED_USAGES => $promoCode->getMaxAllowedUsages(), - PromoCodeDomainObjectAbstract::APPLICABLE_TICKET_IDS => $promoCode->getApplicableTicketIds(), + PromoCodeDomainObjectAbstract::APPLICABLE_PRODUCT_IDS => $promoCode->getApplicableProductIds(), ]); } diff --git a/backend/app/Services/Domain/Question/CreateQuestionService.php b/backend/app/Services/Domain/Question/CreateQuestionService.php index c50672deb4..42ef5ee6e7 100644 --- a/backend/app/Services/Domain/Question/CreateQuestionService.php +++ b/backend/app/Services/Domain/Question/CreateQuestionService.php @@ -24,7 +24,7 @@ public function __construct( */ public function createQuestion( QuestionDomainObject $question, - array $ticketIds, + array $productIds, ): QuestionDomainObject { return $this->databaseManager->transaction(fn() => $this->questionRepository->create([ @@ -36,6 +36,6 @@ public function createQuestion( QuestionDomainObjectAbstract::OPTIONS => $question->getOptions(), QuestionDomainObjectAbstract::IS_HIDDEN => $question->getIsHidden(), QuestionDomainObjectAbstract::DESCRIPTION => $this->purifier->purify($question->getDescription()), - ], $ticketIds)); + ], $productIds)); } } diff --git a/backend/app/Services/Domain/Question/EditQuestionService.php b/backend/app/Services/Domain/Question/EditQuestionService.php index 1c29a8d3c5..784fe93162 100644 --- a/backend/app/Services/Domain/Question/EditQuestionService.php +++ b/backend/app/Services/Domain/Question/EditQuestionService.php @@ -4,7 +4,7 @@ use HiEvents\DomainObjects\Generated\QuestionDomainObjectAbstract; use HiEvents\DomainObjects\QuestionDomainObject; -use HiEvents\DomainObjects\TicketDomainObject; +use HiEvents\DomainObjects\ProductDomainObject; use HiEvents\Repository\Interfaces\QuestionRepositoryInterface; use HTMLPurifier; use Illuminate\Database\DatabaseManager; @@ -25,10 +25,10 @@ public function __construct( */ public function editQuestion( QuestionDomainObject $question, - array $ticketIds, + array $productIds, ): QuestionDomainObject { - return $this->databaseManager->transaction(function () use ($question, $ticketIds) { + return $this->databaseManager->transaction(function () use ($question, $productIds) { $this->questionRepository->updateQuestion( questionId: $question->getId(), eventId: $question->getEventId(), @@ -42,11 +42,11 @@ public function editQuestion( QuestionDomainObjectAbstract::IS_HIDDEN => $question->getIsHidden(), QuestionDomainObjectAbstract::DESCRIPTION => $this->purifier->purify($question->getDescription()), ], - ticketIds: $ticketIds + productIds: $productIds ); return $this->questionRepository - ->loadRelation(TicketDomainObject::class) + ->loadRelation(ProductDomainObject::class) ->findById($question->getId()); }); } diff --git a/backend/app/Services/Domain/Tax/DTO/TaxAndTicketAssociateParams.php b/backend/app/Services/Domain/Tax/DTO/TaxAndProductAssociateParams.php similarity index 71% rename from backend/app/Services/Domain/Tax/DTO/TaxAndTicketAssociateParams.php rename to backend/app/Services/Domain/Tax/DTO/TaxAndProductAssociateParams.php index cd645e87b9..f3b2b5fa70 100644 --- a/backend/app/Services/Domain/Tax/DTO/TaxAndTicketAssociateParams.php +++ b/backend/app/Services/Domain/Tax/DTO/TaxAndProductAssociateParams.php @@ -2,10 +2,10 @@ namespace HiEvents\Services\Domain\Tax\DTO; -class TaxAndTicketAssociateParams +class TaxAndProductAssociateParams { public function __construct( - public readonly int $ticketId, + public readonly int $productId, public readonly int $accountId, public readonly array $taxAndFeeIds, ) diff --git a/backend/app/Services/Domain/Tax/TaxAndFeeCalculationService.php b/backend/app/Services/Domain/Tax/TaxAndFeeCalculationService.php index 641ad7d9a8..f3343269bf 100644 --- a/backend/app/Services/Domain/Tax/TaxAndFeeCalculationService.php +++ b/backend/app/Services/Domain/Tax/TaxAndFeeCalculationService.php @@ -4,8 +4,8 @@ use HiEvents\DomainObjects\Enums\TaxCalculationType; use HiEvents\DomainObjects\TaxAndFeesDomainObject; -use HiEvents\DomainObjects\TicketDomainObject; -use HiEvents\DomainObjects\TicketPriceDomainObject; +use HiEvents\DomainObjects\ProductDomainObject; +use HiEvents\DomainObjects\ProductPriceDomainObject; use HiEvents\Services\Domain\Tax\DTO\TaxCalculationResponse; use InvalidArgumentException; @@ -18,26 +18,26 @@ public function __construct(TaxAndFeeRollupService $taxRollupService) $this->taxRollupService = $taxRollupService; } - public function calculateTaxAndFeesForTicketPrice( - TicketDomainObject $ticket, - TicketPriceDomainObject $price, + public function calculateTaxAndFeesForProductPrice( + ProductDomainObject $product, + ProductPriceDomainObject $price, ): TaxCalculationResponse { - return $this->calculateTaxAndFeesForTicket($ticket, $price->getPrice()); + return $this->calculateTaxAndFeesForProduct($product, $price->getPrice()); } - public function calculateTaxAndFeesForTicket( - TicketDomainObject $ticket, - float $price, - int $quantity = 1 + public function calculateTaxAndFeesForProduct( + ProductDomainObject $product, + float $price, + int $quantity = 1 ): TaxCalculationResponse { $this->taxRollupService->resetRollUp(); - $fees = $ticket->getFees() + $fees = $product->getFees() ?->sum(fn($taxOrFee) => $this->calculateFee($taxOrFee, $price, $quantity)) ?: 0.00; - $taxFees = $ticket->getTaxRates() + $taxFees = $product->getTaxRates() ?->sum(fn($taxOrFee) => $this->calculateFee($taxOrFee, $price + $fees, $quantity)); return new TaxCalculationResponse( diff --git a/backend/app/Services/Domain/Tax/TaxAndTicketAssociationService.php b/backend/app/Services/Domain/Tax/TaxAndProductAssociationService.php similarity index 66% rename from backend/app/Services/Domain/Tax/TaxAndTicketAssociationService.php rename to backend/app/Services/Domain/Tax/TaxAndProductAssociationService.php index f59431faa3..05527137ee 100644 --- a/backend/app/Services/Domain/Tax/TaxAndTicketAssociationService.php +++ b/backend/app/Services/Domain/Tax/TaxAndProductAssociationService.php @@ -4,16 +4,16 @@ use Exception; use HiEvents\Exceptions\InvalidTaxOrFeeIdException; +use HiEvents\Repository\Interfaces\ProductRepositoryInterface; use HiEvents\Repository\Interfaces\TaxAndFeeRepositoryInterface; -use HiEvents\Repository\Interfaces\TicketRepositoryInterface; -use HiEvents\Services\Domain\Tax\DTO\TaxAndTicketAssociateParams; +use HiEvents\Services\Domain\Tax\DTO\TaxAndProductAssociateParams; use Illuminate\Support\Collection; -readonly class TaxAndTicketAssociationService +readonly class TaxAndProductAssociationService { public function __construct( private TaxAndFeeRepositoryInterface $taxAndFeeRepository, - private TicketRepositoryInterface $ticketRepository, + private ProductRepositoryInterface $ticketRepository, ) { } @@ -21,7 +21,7 @@ public function __construct( /** * @throws Exception */ - public function addTaxesToTicket(TaxAndTicketAssociateParams $params): Collection + public function addTaxesToProduct(TaxAndProductAssociateParams $params): Collection { $taxesAndFees = $this->taxAndFeeRepository->findWhereIn( field: 'id', @@ -36,7 +36,7 @@ public function addTaxesToTicket(TaxAndTicketAssociateParams $params): Collectio throw new InvalidTaxOrFeeIdException(__('One or more tax IDs are invalid')); } - $this->ticketRepository->addTaxesAndFeesToTicket($params->ticketId, $params->taxAndFeeIds); + $this->ticketRepository->addTaxesAndFeesToProduct($params->productId, $params->taxAndFeeIds); return $taxesAndFees; } diff --git a/backend/app/Services/Domain/Ticket/AvailableTicketQuantitiesFetchService.php b/backend/app/Services/Domain/Ticket/AvailableTicketQuantitiesFetchService.php deleted file mode 100644 index e3cca4ac33..0000000000 --- a/backend/app/Services/Domain/Ticket/AvailableTicketQuantitiesFetchService.php +++ /dev/null @@ -1,173 +0,0 @@ -config->get('app.homepage_ticket_quantities_cache_ttl')) { - $cachedData = $this->getDataFromCache($eventId); - if ($cachedData) { - return $cachedData; - } - } - - $capacities = $this->capacityAssignmentRepository - ->loadRelation(TicketDomainObject::class) - ->findWhere([ - 'event_id' => $eventId, - 'applies_to' => CapacityAssignmentAppliesTo::TICKETS->name, - 'status' => CapacityAssignmentStatus::ACTIVE->name, - ]); - - $reservedTicketQuantities = $this->fetchReservedTicketQuantities($eventId); - $ticketCapacities = $this->calculateTicketCapacities($capacities); - - $quantities = $reservedTicketQuantities->map(function (AvailableTicketQuantitiesDTO $dto) use ($ticketCapacities) { - $ticketId = $dto->ticket_id; - if (isset($ticketCapacities[$ticketId])) { - $dto->quantity_available = min(array_merge([$dto->quantity_available], $ticketCapacities[$ticketId]->map->getAvailableCapacity()->toArray())); - $dto->capacities = $ticketCapacities[$ticketId]; - } - - return $dto; - }); - - $finalData = new AvailableTicketQuantitiesResponseDTO( - ticketQuantities: $quantities, - capacities: $capacities - ); - - if (!$ignoreCache && $this->config->get('app.homepage_ticket_quantities_cache_ttl')) { - $this->cache->put($this->getCacheKey($eventId), $finalData, $this->config->get('app.homepage_ticket_quantities_cache_ttl')); - } - - return $finalData; - } - - private function fetchReservedTicketQuantities(int $eventId): Collection - { - $result = $this->db->select(<< NOW() - AND orders.deleted_at IS NULL - THEN order_items.quantity - ELSE 0 - END - ) AS quantity_reserved - FROM tickets - JOIN ticket_prices ON tickets.id = ticket_prices.ticket_id - LEFT JOIN order_items ON order_items.ticket_id = tickets.id - AND order_items.ticket_price_id = ticket_prices.id - LEFT JOIN orders ON orders.id = order_items.order_id - AND orders.event_id = tickets.event_id - AND orders.deleted_at IS NULL - WHERE - tickets.event_id = :eventId - AND tickets.deleted_at IS NULL - AND ticket_prices.deleted_at IS NULL - GROUP BY tickets.id, ticket_prices.id - ) - SELECT - tickets.id AS ticket_id, - ticket_prices.id AS ticket_price_id, - tickets.title AS ticket_title, - ticket_prices.label AS price_label, - ticket_prices.initial_quantity_available, - ticket_prices.quantity_sold, - COALESCE( - ticket_prices.initial_quantity_available - - ticket_prices.quantity_sold - - COALESCE(reserved_quantities.quantity_reserved, 0), - 0) AS quantity_available, - COALESCE(reserved_quantities.quantity_reserved, 0) AS quantity_reserved, - CASE WHEN ticket_prices.initial_quantity_available IS NULL - THEN TRUE - ELSE FALSE - END AS unlimited_quantity_available - FROM tickets - JOIN ticket_prices ON tickets.id = ticket_prices.ticket_id - LEFT JOIN reserved_quantities ON tickets.id = reserved_quantities.ticket_id - AND ticket_prices.id = reserved_quantities.ticket_price_id - WHERE - tickets.event_id = :eventId - AND tickets.deleted_at IS NULL - AND ticket_prices.deleted_at IS NULL - GROUP BY tickets.id, ticket_prices.id, reserved_quantities.quantity_reserved; - SQL, [ - 'eventId' => $eventId, - 'reserved' => OrderStatus::RESERVED->name - ]); - - return collect($result)->map(fn($row) => AvailableTicketQuantitiesDTO::fromArray([ - 'ticket_id' => $row->ticket_id, - 'price_id' => $row->ticket_price_id, - 'ticket_title' => $row->ticket_title, - 'price_label' => $row->price_label, - 'quantity_available' => $row->unlimited_quantity_available ? Constants::INFINITE : $row->quantity_available, - 'initial_quantity_available' => $row->initial_quantity_available, - 'quantity_reserved' => $row->quantity_reserved, - 'capacities' => new Collection(), - ])); - } - - /** - * @param Collection $capacities - */ - private function calculateTicketCapacities(Collection $capacities): array - { - $ticketCapacities = []; - foreach ($capacities as $capacity) { - foreach ($capacity->getTickets() as $ticket) { - $ticketId = $ticket->getId(); - if (!isset($ticketCapacities[$ticketId])) { - $ticketCapacities[$ticketId] = collect(); - } - - $ticketCapacities[$ticketId]->push($capacity); - } - } - - return $ticketCapacities; - } - - private function getDataFromCache(int $eventId): ?AvailableTicketQuantitiesResponseDTO - { - return $this->cache->get($this->getCacheKey($eventId)); - } - - private function getCacheKey(int $eventId): string - { - return "event.$eventId.available_ticket_quantities"; - } -} diff --git a/backend/app/Services/Domain/Ticket/CreateTicketService.php b/backend/app/Services/Domain/Ticket/CreateTicketService.php deleted file mode 100644 index dc6764807c..0000000000 --- a/backend/app/Services/Domain/Ticket/CreateTicketService.php +++ /dev/null @@ -1,118 +0,0 @@ -databaseManager->transaction(function () use ($accountId, $taxAndFeeIds, $ticket) { - $persistedTicket = $this->persistTicket($ticket); - - if ($taxAndFeeIds) { - $this->associateTaxesAndFees($persistedTicket, $taxAndFeeIds, $accountId); - } - - return $this->createTicketPrices($persistedTicket, $ticket); - }); - } - - private function persistTicket(TicketDomainObject $ticketsData): TicketDomainObject - { - $event = $this->eventRepository->findById($ticketsData->getEventId()); - - return $this->ticketRepository->create([ - 'title' => $ticketsData->getTitle(), - 'type' => $ticketsData->getType(), - 'order' => $ticketsData->getOrder(), - 'sale_start_date' => $ticketsData->getSaleStartDate() - ? DateHelper::convertToUTC($ticketsData->getSaleStartDate(), $event->getTimezone()) - : null, - 'sale_end_date' => $ticketsData->getSaleEndDate() - ? DateHelper::convertToUTC($ticketsData->getSaleEndDate(), $event->getTimezone()) - : null, - 'max_per_order' => $ticketsData->getMaxPerOrder(), - 'description' => $this->purifier->purify($ticketsData->getDescription()), - 'min_per_order' => $ticketsData->getMinPerOrder(), - 'is_hidden' => $ticketsData->getIsHidden(), - 'hide_before_sale_start_date' => $ticketsData->getHideBeforeSaleStartDate(), - 'hide_after_sale_end_date' => $ticketsData->getHideAfterSaleEndDate(), - 'hide_when_sold_out' => $ticketsData->getHideWhenSoldOut(), - 'start_collapsed' => $ticketsData->getStartCollapsed(), - 'show_quantity_remaining' => $ticketsData->getShowQuantityRemaining(), - 'is_hidden_without_promo_code' => $ticketsData->getIsHiddenWithoutPromoCode(), - 'event_id' => $ticketsData->getEventId(), - ]); - } - - /** - * @throws Exception - */ - private function createTicketTaxesAndFees( - TicketDomainObject $ticket, - array $taxAndFeeIds, - int $accountId, - ): Collection - { - return $this->taxAndTicketAssociationService->addTaxesToTicket( - new TaxAndTicketAssociateParams( - ticketId: $ticket->getId(), - accountId: $accountId, - taxAndFeeIds: $taxAndFeeIds, - ), - ); - } - - /** - * @throws Exception - */ - private function associateTaxesAndFees(TicketDomainObject $persistedTicket, array $taxAndFeeIds, int $accountId): void - { - $persistedTicket->setTaxAndFees($this->createTicketTaxesAndFees( - ticket: $persistedTicket, - taxAndFeeIds: $taxAndFeeIds, - accountId: $accountId, - )); - } - - private function createTicketPrices(TicketDomainObject $persistedTicket, TicketDomainObject $ticket): TicketDomainObject - { - $prices = $this->priceCreateService->createPrices( - ticketId: $persistedTicket->getId(), - prices: $ticket->getTicketPrices(), - event: $this->eventRepository->findById($ticket->getEventId()), - ); - - return $persistedTicket->setTicketPrices($prices); - } -} diff --git a/backend/app/Services/Domain/Ticket/DTO/CreateTicketDTO.php b/backend/app/Services/Domain/Ticket/DTO/CreateTicketDTO.php deleted file mode 100644 index d2008ba496..0000000000 --- a/backend/app/Services/Domain/Ticket/DTO/CreateTicketDTO.php +++ /dev/null @@ -1,10 +0,0 @@ -ticketRepository->findWhere([ - 'event_id' => $eventId, - ])->map(fn(TicketDomainObject $ticket) => $ticket->getId()) - ->toArray(); - - $invalidTicketIds = array_diff($ticketIds, $validTicketIds); - - if (!empty($invalidTicketIds)) { - throw new UnrecognizedTicketIdException( - __('Invalid ticket ids: :ids', ['ids' => implode(', ', $invalidTicketIds)]) - ); - } - } -} diff --git a/backend/app/Services/Domain/Ticket/Exception/UnrecognizedTicketIdException.php b/backend/app/Services/Domain/Ticket/Exception/UnrecognizedTicketIdException.php deleted file mode 100644 index ed1ae38f98..0000000000 --- a/backend/app/Services/Domain/Ticket/Exception/UnrecognizedTicketIdException.php +++ /dev/null @@ -1,10 +0,0 @@ - $tickets - * @param PromoCodeDomainObject|null $promoCode - * @param bool $hideSoldOutTickets - * @return Collection - */ - public function filter( - Collection $tickets, - ?PromoCodeDomainObject $promoCode = null, - bool $hideSoldOutTickets = true, - ): Collection - { - if ($tickets->isEmpty()) { - return $tickets; - } - - $ticketQuantities = $this - ->fetchAvailableTicketQuantitiesService - ->getAvailableTicketQuantities($tickets->first()->getEventId()); - - return $tickets - ->map(fn(TicketDomainObject $ticket) => $this->processTicket($ticket, $ticketQuantities->ticketQuantities, $promoCode)) - ->reject(fn(TicketDomainObject $ticket) => $this->filterTicket($ticket, $promoCode, $hideSoldOutTickets)) - ->each(fn(TicketDomainObject $ticket) => $this->processTicketPrices($ticket, $hideSoldOutTickets)); - } - - private function isHiddenByPromoCode(TicketDomainObject $ticket, ?PromoCodeDomainObject $promoCode): bool - { - return $ticket->getIsHiddenWithoutPromoCode() && !( - $promoCode - && $promoCode->appliesToTicket($ticket) - ); - } - - private function shouldTicketBeDiscounted(?PromoCodeDomainObject $promoCode, TicketDomainObject $ticket): bool - { - if ($ticket->isDonationType() || $ticket->isFreeType()) { - return false; - } - - return $promoCode - && $promoCode->isDiscountCode() - && $promoCode->appliesToTicket($ticket); - } - - /** - * @param PromoCodeDomainObject|null $promoCode - * @param TicketDomainObject $ticket - * @param Collection $ticketQuantities - * @return TicketDomainObject - */ - private function processTicket( - TicketDomainObject $ticket, - Collection $ticketQuantities, - ?PromoCodeDomainObject $promoCode = null, - ): TicketDomainObject - { - if ($this->shouldTicketBeDiscounted($promoCode, $ticket)) { - $ticket->getTicketPrices()?->each(function (TicketPriceDomainObject $price) use ($ticket, $promoCode) { - $price->setPriceBeforeDiscount($price->getPrice()); - $price->setPrice($this->ticketPriceService->getIndividualPrice($ticket, $price, $promoCode)); - }); - } - - $ticket->getTicketPrices()?->map(function (TicketPriceDomainObject $price) use ($ticketQuantities) { - $availableQuantity = $ticketQuantities->where('price_id', $price->getId())->first()?->quantity_available; - $availableQuantity = $availableQuantity === Constants::INFINITE ? null : $availableQuantity; - $price->setQuantityAvailable( - max($availableQuantity, 0) - ); - }); - - // If there is a capacity assigned to the ticket, we set the capacity to capacity available qty, or the sum of all - // ticket prices qty, whichever is lower - $ticketQuantities->each(function (AvailableTicketQuantitiesDTO $quantity) use ($ticket) { - if ($quantity->capacities !== null && $quantity->capacities->isNotEmpty() && $quantity->ticket_id === $ticket->getId()) { - $ticket->setQuantityAvailable( - $quantity->capacities->min(fn(CapacityAssignmentDomainObject $capacity) => $capacity->getAvailableCapacity()) - ); - } - }); - - return $ticket; - } - - private function filterTicket( - TicketDomainObject $ticket, - ?PromoCodeDomainObject $promoCode = null, - bool $hideSoldOutTickets = true, - ): bool - { - $hidden = false; - - if ($this->isHiddenByPromoCode($ticket, $promoCode)) { - $ticket->setOffSaleReason(__('Ticket is hidden without promo code')); - $hidden = true; - } - - if ($ticket->isSoldOut() && $ticket->getHideWhenSoldOut()) { - $ticket->setOffSaleReason(__('Ticket is sold out')); - $hidden = true; - } - - if ($ticket->isBeforeSaleStartDate() && $ticket->getHideBeforeSaleStartDate()) { - $ticket->setOffSaleReason(__('Ticket is before sale start date')); - $hidden = true; - } - - if ($ticket->isAfterSaleEndDate() && $ticket->getHideAfterSaleEndDate()) { - $ticket->setOffSaleReason(__('Ticket is after sale end date')); - $hidden = true; - } - - if ($ticket->getIsHidden()) { - $ticket->setOffSaleReason(__('Ticket is hidden')); - $hidden = true; - } - - return $hidden && $hideSoldOutTickets; - } - - private function processTicketPrice(TicketDomainObject $ticket, TicketPriceDomainObject $price): void - { - $taxAndFees = $this->taxCalculationService - ->calculateTaxAndFeesForTicketPrice($ticket, $price); - - $price - ->setTaxTotal(Currency::round($taxAndFees->taxTotal)) - ->setFeeTotal(Currency::round($taxAndFees->feeTotal)); - - $price->setIsAvailable($this->getPriceAvailability($price, $ticket)); - } - - private function filterTicketPrice( - TicketDomainObject $ticket, - TicketPriceDomainObject $price, - bool $hideSoldOutTickets = true - ): bool - { - $hidden = false; - - if (!$ticket->isTieredType()) { - return false; - } - - if ($price->isBeforeSaleStartDate() && $ticket->getHideBeforeSaleStartDate()) { - $price->setOffSaleReason(__('Price is before sale start date')); - $hidden = true; - } - - if ($price->isAfterSaleEndDate() && $ticket->getHideAfterSaleEndDate()) { - $price->setOffSaleReason(__('Price is after sale end date')); - $hidden = true; - } - - if ($price->isSoldOut() && $ticket->getHideWhenSoldOut()) { - $price->setOffSaleReason(__('Price is sold out')); - $hidden = true; - } - - if ($price->getIsHidden()) { - $price->setOffSaleReason(__('Price is hidden')); - $hidden = true; - } - - return $hidden && $hideSoldOutTickets; - } - - private function processTicketPrices(TicketDomainObject $ticket, bool $hideSoldOutTickets = true): void - { - $ticket->setTicketPrices( - $ticket->getTicketPrices() - ?->each(fn(TicketPriceDomainObject $price) => $this->processTicketPrice($ticket, $price)) - ->reject(fn(TicketPriceDomainObject $price) => $this->filterTicketPrice($ticket, $price, $hideSoldOutTickets)) - ); - } - - /** - * For non-tiered tickets, we can inherit the availability of the ticket. - * - * @param TicketPriceDomainObject $price - * @param TicketDomainObject $ticket - * @return bool - */ - private function getPriceAvailability(TicketPriceDomainObject $price, TicketDomainObject $ticket): bool - { - if ($ticket->isTieredType()) { - return !$price->isSoldOut() - && !$price->isBeforeSaleStartDate() - && !$price->isAfterSaleEndDate() - && !$price->getIsHidden(); - } - - return !$ticket->isSoldOut() - && !$ticket->isBeforeSaleStartDate() - && !$ticket->isAfterSaleEndDate() - && !$ticket->getIsHidden(); - } -} diff --git a/backend/app/Services/Domain/Ticket/TicketPriceService.php b/backend/app/Services/Domain/Ticket/TicketPriceService.php deleted file mode 100644 index b2d652f160..0000000000 --- a/backend/app/Services/Domain/Ticket/TicketPriceService.php +++ /dev/null @@ -1,77 +0,0 @@ -getPrice($ticket, new OrderTicketPriceDTO( - quantity: 1, - price_id: $price->getId(), - ), $promoCode)->price; - } - - public function getPrice( - TicketDomainObject $ticket, - OrderTicketPriceDTO $ticketOrderDetail, - ?PromoCodeDomainObject $promoCode - ): PriceDTO - { - $price = $this->determineTicketPrice($ticket, $ticketOrderDetail); - - if ($ticket->getType() === TicketType::FREE->name) { - return new PriceDTO(0.00); - } - - if ($ticket->getType() === TicketType::DONATION->name) { - return new PriceDTO($price); - } - - if (!$promoCode || !$promoCode->appliesToTicket($ticket)) { - return new PriceDTO($price); - } - - if ($promoCode->getDiscountType() === PromoCodeDiscountTypeEnum::NONE->name) { - return new PriceDTO($price); - } - - if ($promoCode->isFixedDiscount()) { - $discountPrice = Currency::round($price - $promoCode->getDiscount()); - } elseif ($promoCode->isPercentageDiscount()) { - $discountPrice = Currency::round( - $price - ($price * ($promoCode->getDiscount() / 100)) - ); - } else { - $discountPrice = $price; - } - - return new PriceDTO( - price: max(0, $discountPrice), - price_before_discount: $price - ); - } - - private function determineTicketPrice(TicketDomainObject $ticket, OrderTicketPriceDTO $ticketOrderDetails): float - { - return match ($ticket->getType()) { - TicketType::DONATION->name => max($ticket->getPrice(), $ticketOrderDetails->price), - TicketType::PAID->name => $ticket->getPrice(), - TicketType::FREE->name => 0.00, - TicketType::TIERED->name => $ticket->getPriceById($ticketOrderDetails->price_id)?->getPrice() - }; - } -} diff --git a/backend/app/Services/Handlers/Attendee/CreateAttendeeHandler.php b/backend/app/Services/Handlers/Attendee/CreateAttendeeHandler.php index 8202210ac9..6f0dd5ac33 100644 --- a/backend/app/Services/Handlers/Attendee/CreateAttendeeHandler.php +++ b/backend/app/Services/Handlers/Attendee/CreateAttendeeHandler.php @@ -7,26 +7,26 @@ use HiEvents\DomainObjects\Generated\AttendeeDomainObjectAbstract; use HiEvents\DomainObjects\Generated\OrderDomainObjectAbstract; use HiEvents\DomainObjects\Generated\OrderItemDomainObjectAbstract; -use HiEvents\DomainObjects\Generated\TicketDomainObjectAbstract; +use HiEvents\DomainObjects\Generated\ProductDomainObjectAbstract; use HiEvents\DomainObjects\OrderDomainObject; use HiEvents\DomainObjects\OrderItemDomainObject; use HiEvents\DomainObjects\Status\AttendeeStatus; use HiEvents\DomainObjects\Status\OrderPaymentStatus; use HiEvents\DomainObjects\Status\OrderStatus; -use HiEvents\DomainObjects\TicketDomainObject; -use HiEvents\DomainObjects\TicketPriceDomainObject; +use HiEvents\DomainObjects\ProductDomainObject; +use HiEvents\DomainObjects\ProductPriceDomainObject; use HiEvents\Events\OrderStatusChangedEvent; -use HiEvents\Exceptions\InvalidTicketPriceId; -use HiEvents\Exceptions\NoTicketsAvailableException; +use HiEvents\Exceptions\InvalidProductPriceId; +use HiEvents\Exceptions\NoProductsAvailableException; use HiEvents\Helper\IdHelper; use HiEvents\Repository\Interfaces\AttendeeRepositoryInterface; use HiEvents\Repository\Interfaces\EventRepositoryInterface; use HiEvents\Repository\Interfaces\OrderRepositoryInterface; use HiEvents\Repository\Interfaces\TaxAndFeeRepositoryInterface; -use HiEvents\Repository\Interfaces\TicketRepositoryInterface; +use HiEvents\Repository\Interfaces\ProductRepositoryInterface; use HiEvents\Services\Domain\Order\OrderManagementService; use HiEvents\Services\Domain\Tax\TaxAndFeeRollupService; -use HiEvents\Services\Domain\Ticket\TicketQuantityUpdateService; +use HiEvents\Services\Domain\Product\ProductQuantityUpdateService; use HiEvents\Services\Handlers\Attendee\DTO\CreateAttendeeDTO; use HiEvents\Services\Handlers\Attendee\DTO\CreateAttendeeTaxAndFeeDTO; use Illuminate\Database\DatabaseManager; @@ -39,9 +39,9 @@ class CreateAttendeeHandler public function __construct( private readonly AttendeeRepositoryInterface $attendeeRepository, private readonly OrderRepositoryInterface $orderRepository, - private readonly TicketRepositoryInterface $ticketRepository, + private readonly ProductRepositoryInterface $productRepository, private readonly EventRepositoryInterface $eventRepository, - private readonly TicketQuantityUpdateService $ticketQuantityAdjustmentService, + private readonly ProductQuantityUpdateService $productQuantityAdjustmentService, private readonly DatabaseManager $databaseManager, private readonly TaxAndFeeRepositoryInterface $taxAndFeeRepository, private readonly TaxAndFeeRollupService $taxAndFeeRollupService, @@ -51,7 +51,7 @@ public function __construct( } /** - * @throws NoTicketsAvailableException + * @throws NoProductsAvailableException * @throws Throwable */ public function handle(CreateAttendeeDTO $attendeeDTO): AttendeeDomainObject @@ -61,30 +61,30 @@ public function handle(CreateAttendeeDTO $attendeeDTO): AttendeeDomainObject $order = $this->createOrder($attendeeDTO->event_id, $attendeeDTO); - /** @var TicketDomainObject $ticket */ - $ticket = $this->ticketRepository - ->loadRelation(TicketPriceDomainObject::class) + /** @var ProductDomainObject $product */ + $product = $this->productRepository + ->loadRelation(ProductPriceDomainObject::class) ->findFirstWhere([ - TicketDomainObjectAbstract::ID => $attendeeDTO->ticket_id, - TicketDomainObjectAbstract::EVENT_ID => $attendeeDTO->event_id, + ProductDomainObjectAbstract::ID => $attendeeDTO->product_id, + ProductDomainObjectAbstract::EVENT_ID => $attendeeDTO->event_id, ]); - $availableQuantity = $this->ticketRepository->getQuantityRemainingForTicketPrice( - $attendeeDTO->ticket_id, - $attendeeDTO->ticket_price_id, + $availableQuantity = $this->productRepository->getQuantityRemainingForProductPrice( + $attendeeDTO->product_id, + $attendeeDTO->product_price_id, ); if ($availableQuantity <= 0) { - throw new NoTicketsAvailableException(__('There are no tickets available. ' . - 'If you would like to assign a ticket to this attendee,' . - ' please adjust the ticket\'s available quantity.')); + throw new NoProductsAvailableException(__('There are no products available. ' . + 'If you would like to assign a product to this attendee,' . + ' please adjust the product\'s available quantity.')); } - $ticketPriceId = $this->getTicketPriceId($attendeeDTO, $ticket); + $productPriceId = $this->getProductPriceId($attendeeDTO, $product); $this->processTaxesAndFees($attendeeDTO); - $orderItem = $this->createOrderItem($attendeeDTO, $order, $ticket, $ticketPriceId); + $orderItem = $this->createOrderItem($attendeeDTO, $order, $product, $productPriceId); $attendee = $this->createAttendee($order, $attendeeDTO); @@ -122,27 +122,27 @@ private function createOrder(int $eventId, CreateAttendeeDTO $attendeeDTO): Orde } /** - * @throws InvalidTicketPriceId + * @throws InvalidProductPriceId */ - private function getTicketPriceId(CreateAttendeeDTO $attendeeDTO, TicketDomainObject $ticket): int + private function getProductPriceId(CreateAttendeeDTO $attendeeDTO, ProductDomainObject $product): int { - $priceIds = $ticket->getTicketPrices()->map(fn(TicketPriceDomainObject $ticketPrice) => $ticketPrice->getId()); + $priceIds = $product->getProductPrices()->map(fn(ProductPriceDomainObject $productPrice) => $productPrice->getId()); - if ($attendeeDTO->ticket_price_id) { - if (!$priceIds->contains($attendeeDTO->ticket_price_id)) { - throw new InvalidTicketPriceId(__('The ticket price ID is invalid.')); + if ($attendeeDTO->product_price_id) { + if (!$priceIds->contains($attendeeDTO->product_price_id)) { + throw new InvalidProductPriceId(__('The product price ID is invalid.')); } - return $attendeeDTO->ticket_price_id; + return $attendeeDTO->product_price_id; } - /** @var TicketPriceDomainObject $ticketPrice */ - $ticketPrice = $ticket->getTicketPrices()->first(); + /** @var ProductPriceDomainObject $productPrice */ + $productPrice = $product->getProductPrices()->first(); - if ($ticketPrice) { - return $ticketPrice->getId(); + if ($productPrice) { + return $productPrice->getId(); } - throw new InvalidTicketPriceId(__('The ticket price ID is invalid.')); + throw new InvalidProductPriceId(__('The product price ID is invalid.')); } private function calculateTaxesAndFees(CreateAttendeeDTO $attendeeDTO): ?Collection @@ -186,11 +186,11 @@ private function processTaxesAndFees(CreateAttendeeDTO $attendeeDTO): void ); } - private function createOrderItem(CreateAttendeeDTO $attendeeDTO, OrderDomainObject $order, TicketDomainObject $ticket, int $ticketPriceId): OrderItemDomainObject + private function createOrderItem(CreateAttendeeDTO $attendeeDTO, OrderDomainObject $order, ProductDomainObject $product, int $productPriceId): OrderItemDomainObject { return $this->orderRepository->addOrderItem( [ - OrderItemDomainObjectAbstract::TICKET_ID => $attendeeDTO->ticket_id, + OrderItemDomainObjectAbstract::PRODUCT_ID => $attendeeDTO->product_id, OrderItemDomainObjectAbstract::QUANTITY => 1, OrderItemDomainObjectAbstract::TOTAL_BEFORE_ADDITIONS => $attendeeDTO->amount_paid, OrderItemDomainObjectAbstract::TOTAL_GROSS => $attendeeDTO->amount_paid + $this->taxAndFeeRollupService->getTotalTaxesAndFees(), @@ -198,8 +198,8 @@ private function createOrderItem(CreateAttendeeDTO $attendeeDTO, OrderDomainObje OrderItemDomainObjectAbstract::TOTAL_SERVICE_FEE => $this->taxAndFeeRollupService->getTotalFees(), OrderItemDomainObjectAbstract::PRICE => $attendeeDTO->amount_paid, OrderItemDomainObjectAbstract::ORDER_ID => $order->getId(), - OrderItemDomainObjectAbstract::ITEM_NAME => $ticket->getTitle(), - OrderItemDomainObjectAbstract::TICKET_PRICE_ID => $ticketPriceId, + OrderItemDomainObjectAbstract::ITEM_NAME => $product->getTitle(), + OrderItemDomainObjectAbstract::PRODUCT_PRICE_ID => $productPriceId, OrderItemDomainObjectAbstract::TAXES_AND_FEES_ROLLUP => $this->taxAndFeeRollupService->getRollUp(), ] ); @@ -209,8 +209,8 @@ private function createAttendee(OrderDomainObject $order, CreateAttendeeDTO $att { return $this->attendeeRepository->create([ AttendeeDomainObjectAbstract::EVENT_ID => $order->getEventId(), - AttendeeDomainObjectAbstract::TICKET_ID => $attendeeDTO->ticket_id, - AttendeeDomainObjectAbstract::TICKET_PRICE_ID => $attendeeDTO->ticket_price_id, + AttendeeDomainObjectAbstract::PRODUCT_ID => $attendeeDTO->product_id, + AttendeeDomainObjectAbstract::PRODUCT_PRICE_ID => $attendeeDTO->product_price_id, AttendeeDomainObjectAbstract::STATUS => AttendeeStatus::ACTIVE->name, AttendeeDomainObjectAbstract::EMAIL => $attendeeDTO->email, AttendeeDomainObjectAbstract::FIRST_NAME => $attendeeDTO->first_name, @@ -224,8 +224,8 @@ private function createAttendee(OrderDomainObject $order, CreateAttendeeDTO $att private function fireEventsAndUpdateQuantities(CreateAttendeeDTO $attendeeDTO, OrderDomainObject $order): void { - $this->ticketQuantityAdjustmentService->increaseQuantitySold( - priceId: $attendeeDTO->ticket_price_id, + $this->productQuantityAdjustmentService->increaseQuantitySold( + priceId: $attendeeDTO->product_price_id, ); event(new OrderStatusChangedEvent( diff --git a/backend/app/Services/Handlers/Attendee/DTO/CreateAttendeeDTO.php b/backend/app/Services/Handlers/Attendee/DTO/CreateAttendeeDTO.php index f7c852efc5..f06d9f0b2d 100644 --- a/backend/app/Services/Handlers/Attendee/DTO/CreateAttendeeDTO.php +++ b/backend/app/Services/Handlers/Attendee/DTO/CreateAttendeeDTO.php @@ -12,13 +12,13 @@ public function __construct( public readonly string $first_name, public readonly string $last_name, public readonly string $email, - public readonly int $ticket_id, + public readonly int $product_id, public readonly int $event_id, public readonly bool $send_confirmation_email, public readonly float $amount_paid, public readonly string $locale, public readonly ?bool $amount_includes_tax = false, - public readonly ?int $ticket_price_id = null, + public readonly ?int $product_price_id = null, #[CollectionOf(CreateAttendeeTaxAndFeeDTO::class)] public readonly ?Collection $taxes_and_fees = null, ) diff --git a/backend/app/Services/Handlers/Attendee/DTO/EditAttendeeDTO.php b/backend/app/Services/Handlers/Attendee/DTO/EditAttendeeDTO.php index 79b486591e..5ded58cc90 100644 --- a/backend/app/Services/Handlers/Attendee/DTO/EditAttendeeDTO.php +++ b/backend/app/Services/Handlers/Attendee/DTO/EditAttendeeDTO.php @@ -10,8 +10,8 @@ public function __construct( public string $first_name, public string $last_name, public string $email, - public int $ticket_id, - public int $ticket_price_id, + public int $product_id, + public int $product_price_id, public int $event_id, public int $attendee_id, ) diff --git a/backend/app/Services/Handlers/Attendee/EditAttendeeHandler.php b/backend/app/Services/Handlers/Attendee/EditAttendeeHandler.php index 48f3d739db..1d1257e059 100644 --- a/backend/app/Services/Handlers/Attendee/EditAttendeeHandler.php +++ b/backend/app/Services/Handlers/Attendee/EditAttendeeHandler.php @@ -3,14 +3,14 @@ namespace HiEvents\Services\Handlers\Attendee; use HiEvents\DomainObjects\AttendeeDomainObject; -use HiEvents\DomainObjects\Enums\TicketType; +use HiEvents\DomainObjects\Enums\ProductPriceType; use HiEvents\DomainObjects\Generated\AttendeeDomainObjectAbstract; -use HiEvents\DomainObjects\Generated\TicketDomainObjectAbstract; -use HiEvents\DomainObjects\TicketPriceDomainObject; -use HiEvents\Exceptions\NoTicketsAvailableException; +use HiEvents\DomainObjects\Generated\ProductDomainObjectAbstract; +use HiEvents\DomainObjects\ProductPriceDomainObject; +use HiEvents\Exceptions\NoProductsAvailableException; use HiEvents\Repository\Interfaces\AttendeeRepositoryInterface; -use HiEvents\Repository\Interfaces\TicketRepositoryInterface; -use HiEvents\Services\Domain\Ticket\TicketQuantityUpdateService; +use HiEvents\Repository\Interfaces\ProductRepositoryInterface; +use HiEvents\Services\Domain\Product\ProductQuantityUpdateService; use HiEvents\Services\Handlers\Attendee\DTO\EditAttendeeDTO; use Illuminate\Database\DatabaseManager; use Illuminate\Validation\ValidationException; @@ -19,10 +19,10 @@ class EditAttendeeHandler { public function __construct( - private readonly AttendeeRepositoryInterface $attendeeRepository, - private readonly TicketRepositoryInterface $ticketRepository, - private readonly TicketQuantityUpdateService $ticketQuantityService, - private readonly DatabaseManager $databaseManager, + private readonly AttendeeRepositoryInterface $attendeeRepository, + private readonly ProductRepositoryInterface $productRepository, + private readonly ProductQuantityUpdateService $productQuantityService, + private readonly DatabaseManager $databaseManager, ) { } @@ -34,21 +34,21 @@ public function __construct( public function handle(EditAttendeeDTO $editAttendeeDTO): AttendeeDomainObject { return $this->databaseManager->transaction(function () use ($editAttendeeDTO) { - $this->validateTicketId($editAttendeeDTO); + $this->validateProductId($editAttendeeDTO); $attendee = $this->getAttendee($editAttendeeDTO); - $this->adjustTicketQuantities($attendee, $editAttendeeDTO); + $this->adjustProductQuantities($attendee, $editAttendeeDTO); return $this->updateAttendee($editAttendeeDTO); }); } - private function adjustTicketQuantities(AttendeeDomainObject $attendee, EditAttendeeDTO $editAttendeeDTO): void + private function adjustProductQuantities(AttendeeDomainObject $attendee, EditAttendeeDTO $editAttendeeDTO): void { - if ($attendee->getTicketPriceId() !== $editAttendeeDTO->ticket_price_id) { - $this->ticketQuantityService->decreaseQuantitySold($editAttendeeDTO->ticket_price_id); - $this->ticketQuantityService->increaseQuantitySold($attendee->getTicketPriceId()); + if ($attendee->getProductPriceId() !== $editAttendeeDTO->product_price_id) { + $this->productQuantityService->decreaseQuantitySold($editAttendeeDTO->product_price_id); + $this->productQuantityService->increaseQuantitySold($attendee->getProductPriceId()); } } @@ -58,7 +58,7 @@ private function updateAttendee(EditAttendeeDTO $editAttendeeDTO): AttendeeDomai 'first_name' => $editAttendeeDTO->first_name, 'last_name' => $editAttendeeDTO->last_name, 'email' => $editAttendeeDTO->email, - 'ticket_id' => $editAttendeeDTO->ticket_id, + 'product_id' => $editAttendeeDTO->product_id, ], [ 'event_id' => $editAttendeeDTO->event_id, ]); @@ -66,32 +66,32 @@ private function updateAttendee(EditAttendeeDTO $editAttendeeDTO): AttendeeDomai /** * @throws ValidationException - * @throws NoTicketsAvailableException + * @throws NoProductsAvailableException */ - private function validateTicketId(EditAttendeeDTO $editAttendeeDTO): void + private function validateProductId(EditAttendeeDTO $editAttendeeDTO): void { - $ticket = $this->ticketRepository - ->loadRelation(TicketPriceDomainObject::class) + $product = $this->productRepository + ->loadRelation(ProductPriceDomainObject::class) ->findFirstWhere([ - TicketDomainObjectAbstract::ID => $editAttendeeDTO->ticket_id, + ProductDomainObjectAbstract::ID => $editAttendeeDTO->product_id, ]); - if ($ticket->getEventId() !== $editAttendeeDTO->event_id) { + if ($product->getEventId() !== $editAttendeeDTO->event_id) { throw ValidationException::withMessages([ - 'ticket_id' => __('Ticket ID is not valid'), + 'product_id' => __('Product ID is not valid'), ]); } - $availableQuantity = $this->ticketRepository->getQuantityRemainingForTicketPrice( - ticketId: $editAttendeeDTO->ticket_id, - ticketPriceId: $ticket->getType() === TicketType::TIERED->name - ? $editAttendeeDTO->ticket_price_id - : $ticket->getTicketPrices()->first()->getId(), + $availableQuantity = $this->productRepository->getQuantityRemainingForProductPrice( + productId: $editAttendeeDTO->product_id, + productPriceId: $product->getType() === ProductPriceType::TIERED->name + ? $editAttendeeDTO->product_price_id + : $product->getProductPrices()->first()->getId(), ); if ($availableQuantity <= 0) { - throw new NoTicketsAvailableException( - __('There are no tickets available. If you would like to assign this ticket to this attendee, please adjust the ticket\'s available quantity.') + throw new NoProductsAvailableException( + __('There are no products available. If you would like to assign this product to this attendee, please adjust the product\'s available quantity.') ); } } diff --git a/backend/app/Services/Handlers/Attendee/PartialEditAttendeeHandler.php b/backend/app/Services/Handlers/Attendee/PartialEditAttendeeHandler.php index 5717bddfb7..1283559e46 100644 --- a/backend/app/Services/Handlers/Attendee/PartialEditAttendeeHandler.php +++ b/backend/app/Services/Handlers/Attendee/PartialEditAttendeeHandler.php @@ -5,7 +5,7 @@ use HiEvents\DomainObjects\AttendeeDomainObject; use HiEvents\DomainObjects\Status\AttendeeStatus; use HiEvents\Repository\Interfaces\AttendeeRepositoryInterface; -use HiEvents\Services\Domain\Ticket\TicketQuantityUpdateService; +use HiEvents\Services\Domain\Product\ProductQuantityUpdateService; use HiEvents\Services\Handlers\Attendee\DTO\PartialEditAttendeeDTO; use Illuminate\Database\DatabaseManager; use Symfony\Component\Routing\Exception\ResourceNotFoundException; @@ -14,9 +14,9 @@ class PartialEditAttendeeHandler { public function __construct( - private readonly AttendeeRepositoryInterface $attendeeRepository, - private readonly TicketQuantityUpdateService $ticketQuantityService, - private readonly DatabaseManager $databaseManager + private readonly AttendeeRepositoryInterface $attendeeRepository, + private readonly ProductQuantityUpdateService $productQuantityService, + private readonly DatabaseManager $databaseManager ) { } @@ -43,7 +43,7 @@ private function updateAttendee(PartialEditAttendeeDTO $data): AttendeeDomainObj } if ($data->status && $data->status !== $attendee->getStatus()) { - $this->adjustTicketQuantity($data, $attendee); + $this->adjustProductQuantity($data, $attendee); } return $this->attendeeRepository->updateByIdWhere( @@ -62,14 +62,14 @@ private function updateAttendee(PartialEditAttendeeDTO $data): AttendeeDomainObj } /** - * @todo - we should check ticket availability before updating the ticket quantity + * @todo - we should check product availability before updating the product quantity */ - private function adjustTicketQuantity(PartialEditAttendeeDTO $data, AttendeeDomainObject $attendee): void + private function adjustProductQuantity(PartialEditAttendeeDTO $data, AttendeeDomainObject $attendee): void { if ($data->status === AttendeeStatus::ACTIVE->name) { - $this->ticketQuantityService->increaseQuantitySold($attendee->getTicketPriceId()); + $this->productQuantityService->increaseQuantitySold($attendee->getProductPriceId()); } elseif ($data->status === AttendeeStatus::CANCELLED->name) { - $this->ticketQuantityService->decreaseQuantitySold($attendee->getTicketPriceId()); + $this->productQuantityService->decreaseQuantitySold($attendee->getProductPriceId()); } } } diff --git a/backend/app/Services/Handlers/Attendee/ResendAttendeeTicketHandler.php b/backend/app/Services/Handlers/Attendee/ResendAttendeeTicketHandler.php index 1424a16293..95ba84dcd5 100644 --- a/backend/app/Services/Handlers/Attendee/ResendAttendeeTicketHandler.php +++ b/backend/app/Services/Handlers/Attendee/ResendAttendeeTicketHandler.php @@ -17,7 +17,7 @@ readonly class ResendAttendeeTicketHandler { public function __construct( - private SendAttendeeTicketService $sendAttendeeTicketService, + private SendAttendeeTicketService $sendAttendeeProductService, private AttendeeRepositoryInterface $attendeeRepository, private EventRepositoryInterface $eventRepository, private LoggerInterface $logger, @@ -28,11 +28,11 @@ public function __construct( /** * @throws ResourceConflictException */ - public function handle(ResendAttendeeTicketDTO $resendAttendeeTicketDTO): void + public function handle(ResendAttendeeTicketDTO $resendAttendeeProductDTO): void { $attendee = $this->attendeeRepository->findFirstWhere([ - 'id' => $resendAttendeeTicketDTO->attendeeId, - 'event_id' => $resendAttendeeTicketDTO->eventId, + 'id' => $resendAttendeeProductDTO->attendeeId, + 'event_id' => $resendAttendeeProductDTO->eventId, ]); if (!$attendee) { @@ -46,9 +46,9 @@ public function handle(ResendAttendeeTicketDTO $resendAttendeeTicketDTO): void $event = $this->eventRepository ->loadRelation(new Relationship(OrganizerDomainObject::class, name: 'organizer')) ->loadRelation(EventSettingDomainObject::class) - ->findById($resendAttendeeTicketDTO->eventId); + ->findById($resendAttendeeProductDTO->eventId); - $this->sendAttendeeTicketService->send( + $this->sendAttendeeProductService->send( attendee: $attendee, event: $event, eventSettings: $event->getEventSettings(), @@ -56,8 +56,8 @@ public function handle(ResendAttendeeTicketDTO $resendAttendeeTicketDTO): void ); $this->logger->info('Attendee ticket resent', [ - 'attendeeId' => $resendAttendeeTicketDTO->attendeeId, - 'eventId' => $resendAttendeeTicketDTO->eventId + 'attendeeId' => $resendAttendeeProductDTO->attendeeId, + 'eventId' => $resendAttendeeProductDTO->eventId ]); } } diff --git a/backend/app/Services/Handlers/CapacityAssignment/CreateCapacityAssignmentHandler.php b/backend/app/Services/Handlers/CapacityAssignment/CreateCapacityAssignmentHandler.php index bfa77f47b3..26284f36c9 100644 --- a/backend/app/Services/Handlers/CapacityAssignment/CreateCapacityAssignmentHandler.php +++ b/backend/app/Services/Handlers/CapacityAssignment/CreateCapacityAssignmentHandler.php @@ -5,7 +5,7 @@ use HiEvents\DomainObjects\CapacityAssignmentDomainObject; use HiEvents\DomainObjects\Enums\CapacityAssignmentAppliesTo; use HiEvents\Services\Domain\CapacityAssignment\CreateCapacityAssignmentService; -use HiEvents\Services\Domain\Ticket\Exception\UnrecognizedTicketIdException; +use HiEvents\Services\Domain\Product\Exception\UnrecognizedProductIdException; use HiEvents\Services\Handlers\CapacityAssignment\DTO\UpsertCapacityAssignmentDTO; class CreateCapacityAssignmentHandler @@ -17,7 +17,7 @@ public function __construct( } /** - * @throws UnrecognizedTicketIdException + * @throws UnrecognizedProductIdException */ public function handle(UpsertCapacityAssignmentDTO $data): CapacityAssignmentDomainObject { @@ -25,12 +25,12 @@ public function handle(UpsertCapacityAssignmentDTO $data): CapacityAssignmentDom ->setName($data->name) ->setEventId($data->event_id) ->setCapacity($data->capacity) - ->setAppliesTo(CapacityAssignmentAppliesTo::TICKETS->name) + ->setAppliesTo(CapacityAssignmentAppliesTo::PRODUCTS->name) ->setStatus($data->status->name); return $this->createCapacityAssignmentService->createCapacityAssignment( $capacityAssignment, - $data->ticket_ids, + $data->product_ids, ); } } diff --git a/backend/app/Services/Handlers/CapacityAssignment/DTO/UpsertCapacityAssignmentDTO.php b/backend/app/Services/Handlers/CapacityAssignment/DTO/UpsertCapacityAssignmentDTO.php index 35669261c0..7ebd41bef3 100644 --- a/backend/app/Services/Handlers/CapacityAssignment/DTO/UpsertCapacityAssignmentDTO.php +++ b/backend/app/Services/Handlers/CapacityAssignment/DTO/UpsertCapacityAssignmentDTO.php @@ -13,7 +13,7 @@ public function __construct( public CapacityAssignmentStatus $status, public ?int $capacity, - public ?array $ticket_ids = null, + public ?array $product_ids = null, public ?int $id = null, ) { diff --git a/backend/app/Services/Handlers/CapacityAssignment/DeleteCapacityAssignmentHandler.php b/backend/app/Services/Handlers/CapacityAssignment/DeleteCapacityAssignmentHandler.php index bda3eac0ba..26b90b3f5c 100644 --- a/backend/app/Services/Handlers/CapacityAssignment/DeleteCapacityAssignmentHandler.php +++ b/backend/app/Services/Handlers/CapacityAssignment/DeleteCapacityAssignmentHandler.php @@ -3,14 +3,14 @@ namespace HiEvents\Services\Handlers\CapacityAssignment; use HiEvents\Repository\Interfaces\CapacityAssignmentRepositoryInterface; -use HiEvents\Repository\Interfaces\TicketRepositoryInterface; +use HiEvents\Repository\Interfaces\ProductRepositoryInterface; use Illuminate\Database\DatabaseManager; class DeleteCapacityAssignmentHandler { public function __construct( private readonly CapacityAssignmentRepositoryInterface $capacityAssignmentRepository, - private readonly TicketRepositoryInterface $ticketRepository, + private readonly ProductRepositoryInterface $productRepository, private readonly DatabaseManager $databaseManager, ) { @@ -19,7 +19,7 @@ public function __construct( public function handle(int $id, int $eventId): void { $this->databaseManager->transaction(function () use ($id, $eventId) { - $this->ticketRepository->removeCapacityAssignmentFromTickets( + $this->productRepository->removeCapacityAssignmentFromProducts( capacityAssignmentId: $id, ); diff --git a/backend/app/Services/Handlers/CapacityAssignment/GetCapacityAssignmentHandler.php b/backend/app/Services/Handlers/CapacityAssignment/GetCapacityAssignmentHandler.php index cbdf770f03..57c8e22d7e 100644 --- a/backend/app/Services/Handlers/CapacityAssignment/GetCapacityAssignmentHandler.php +++ b/backend/app/Services/Handlers/CapacityAssignment/GetCapacityAssignmentHandler.php @@ -3,7 +3,7 @@ namespace HiEvents\Services\Handlers\CapacityAssignment; use HiEvents\DomainObjects\CapacityAssignmentDomainObject; -use HiEvents\DomainObjects\TicketDomainObject; +use HiEvents\DomainObjects\ProductDomainObject; use HiEvents\Repository\Interfaces\CapacityAssignmentRepositoryInterface; use Symfony\Component\Routing\Exception\ResourceNotFoundException; @@ -18,7 +18,7 @@ public function __construct( public function handle(int $capacityAssignmentId, int $eventId): CapacityAssignmentDomainObject { $capacityAssignment = $this->capacityAssignmentRepository - ->loadRelation(TicketDomainObject::class) + ->loadRelation(ProductDomainObject::class) ->findFirstWhere([ 'event_id' => $eventId, 'id' => $capacityAssignmentId, diff --git a/backend/app/Services/Handlers/CapacityAssignment/GetCapacityAssignmentsHandler.php b/backend/app/Services/Handlers/CapacityAssignment/GetCapacityAssignmentsHandler.php index e4d0434b3b..8b0a0cb2a6 100644 --- a/backend/app/Services/Handlers/CapacityAssignment/GetCapacityAssignmentsHandler.php +++ b/backend/app/Services/Handlers/CapacityAssignment/GetCapacityAssignmentsHandler.php @@ -2,7 +2,7 @@ namespace HiEvents\Services\Handlers\CapacityAssignment; -use HiEvents\DomainObjects\TicketDomainObject; +use HiEvents\DomainObjects\ProductDomainObject; use HiEvents\Repository\Interfaces\CapacityAssignmentRepositoryInterface; use HiEvents\Services\Handlers\CapacityAssignment\DTO\GetCapacityAssignmentsDTO; use Illuminate\Contracts\Pagination\LengthAwarePaginator; @@ -18,7 +18,7 @@ public function __construct( public function handle(GetCapacityAssignmentsDTO $dto): LengthAwarePaginator { return $this->capacityAssignmentRepository - ->loadRelation(TicketDomainObject::class) + ->loadRelation(ProductDomainObject::class) ->findByEventId( eventId: $dto->eventId, params: $dto->queryParams, diff --git a/backend/app/Services/Handlers/CapacityAssignment/UpdateCapacityAssignmentHandler.php b/backend/app/Services/Handlers/CapacityAssignment/UpdateCapacityAssignmentHandler.php index 9c330d859b..d75f7f86f1 100644 --- a/backend/app/Services/Handlers/CapacityAssignment/UpdateCapacityAssignmentHandler.php +++ b/backend/app/Services/Handlers/CapacityAssignment/UpdateCapacityAssignmentHandler.php @@ -5,7 +5,7 @@ use HiEvents\DomainObjects\CapacityAssignmentDomainObject; use HiEvents\DomainObjects\Enums\CapacityAssignmentAppliesTo; use HiEvents\Services\Domain\CapacityAssignment\UpdateCapacityAssignmentService; -use HiEvents\Services\Domain\Ticket\Exception\UnrecognizedTicketIdException; +use HiEvents\Services\Domain\Product\Exception\UnrecognizedProductIdException; use HiEvents\Services\Handlers\CapacityAssignment\DTO\UpsertCapacityAssignmentDTO; class UpdateCapacityAssignmentHandler @@ -17,7 +17,7 @@ public function __construct( } /** - * @throws UnrecognizedTicketIdException + * @throws UnrecognizedProductIdException */ public function handle(UpsertCapacityAssignmentDTO $data): CapacityAssignmentDomainObject { @@ -26,12 +26,12 @@ public function handle(UpsertCapacityAssignmentDTO $data): CapacityAssignmentDom ->setName($data->name) ->setEventId($data->event_id) ->setCapacity($data->capacity) - ->setAppliesTo(CapacityAssignmentAppliesTo::TICKETS->name) + ->setAppliesTo(CapacityAssignmentAppliesTo::PRODUCTS->name) ->setStatus($data->status->name); return $this->updateCapacityAssignmentService->updateCapacityAssignment( $capacityAssignment, - $data->ticket_ids, + $data->product_ids, ); } } diff --git a/backend/app/Services/Handlers/CheckInList/CreateCheckInListHandler.php b/backend/app/Services/Handlers/CheckInList/CreateCheckInListHandler.php index a3d5ed4554..da0ee904ad 100644 --- a/backend/app/Services/Handlers/CheckInList/CreateCheckInListHandler.php +++ b/backend/app/Services/Handlers/CheckInList/CreateCheckInListHandler.php @@ -4,7 +4,7 @@ use HiEvents\DomainObjects\CheckInListDomainObject; use HiEvents\Services\Domain\CheckInList\CreateCheckInListService; -use HiEvents\Services\Domain\Ticket\Exception\UnrecognizedTicketIdException; +use HiEvents\Services\Domain\Product\Exception\UnrecognizedProductIdException; use HiEvents\Services\Handlers\CheckInList\DTO\UpsertCheckInListDTO; class CreateCheckInListHandler @@ -16,7 +16,7 @@ public function __construct( } /** - * @throws UnrecognizedTicketIdException + * @throws UnrecognizedProductIdException */ public function handle(UpsertCheckInListDTO $listData): CheckInListDomainObject { @@ -29,7 +29,7 @@ public function handle(UpsertCheckInListDTO $listData): CheckInListDomainObject return $this->createCheckInListService->createCheckInList( checkInList: $checkInList, - ticketIds: $listData->ticketIds + productIds: $listData->productIds ); } } diff --git a/backend/app/Services/Handlers/CheckInList/DTO/UpsertCheckInListDTO.php b/backend/app/Services/Handlers/CheckInList/DTO/UpsertCheckInListDTO.php index 35348163e4..16296943e1 100644 --- a/backend/app/Services/Handlers/CheckInList/DTO/UpsertCheckInListDTO.php +++ b/backend/app/Services/Handlers/CheckInList/DTO/UpsertCheckInListDTO.php @@ -10,7 +10,7 @@ public function __construct( public string $name, public ?string $description, public int $eventId, - public array $ticketIds, + public array $productIds, public ?string $expiresAt = null, public ?string $activatesAt = null, public ?int $id = null, diff --git a/backend/app/Services/Handlers/CheckInList/GetCheckInListHandler.php b/backend/app/Services/Handlers/CheckInList/GetCheckInListHandler.php index fc442f76ba..83c40b12d1 100644 --- a/backend/app/Services/Handlers/CheckInList/GetCheckInListHandler.php +++ b/backend/app/Services/Handlers/CheckInList/GetCheckInListHandler.php @@ -4,7 +4,7 @@ use HiEvents\DomainObjects\CheckInListDomainObject; use HiEvents\DomainObjects\EventDomainObject; -use HiEvents\DomainObjects\TicketDomainObject; +use HiEvents\DomainObjects\ProductDomainObject; use HiEvents\Repository\Eloquent\Value\Relationship; use HiEvents\Repository\Interfaces\CheckInListRepositoryInterface; use Symfony\Component\Routing\Exception\ResourceNotFoundException; @@ -20,7 +20,7 @@ public function __construct( public function handle(int $checkInListId, int $eventId): CheckInListDomainObject { $checkInList = $this->checkInListRepository - ->loadRelation(TicketDomainObject::class) + ->loadRelation(ProductDomainObject::class) ->loadRelation(new Relationship(domainObject: EventDomainObject::class, name: 'event')) ->findFirstWhere([ 'event_id' => $eventId, diff --git a/backend/app/Services/Handlers/CheckInList/GetCheckInListsHandler.php b/backend/app/Services/Handlers/CheckInList/GetCheckInListsHandler.php index be46595b23..aff2b2182a 100644 --- a/backend/app/Services/Handlers/CheckInList/GetCheckInListsHandler.php +++ b/backend/app/Services/Handlers/CheckInList/GetCheckInListsHandler.php @@ -4,7 +4,7 @@ use HiEvents\DomainObjects\CheckInListDomainObject; use HiEvents\DomainObjects\EventDomainObject; -use HiEvents\DomainObjects\TicketDomainObject; +use HiEvents\DomainObjects\ProductDomainObject; use HiEvents\Repository\Eloquent\Value\Relationship; use HiEvents\Repository\Interfaces\CheckInListRepositoryInterface; use HiEvents\Services\Handlers\CheckInList\DTO\GetCheckInListsDTO; @@ -21,7 +21,7 @@ public function __construct( public function handle(GetCheckInListsDTO $dto): LengthAwarePaginator { $checkInLists = $this->checkInListRepository - ->loadRelation(TicketDomainObject::class) + ->loadRelation(ProductDomainObject::class) ->loadRelation(new Relationship(domainObject: EventDomainObject::class, name: 'event')) ->findByEventId( eventId: $dto->eventId, diff --git a/backend/app/Services/Handlers/CheckInList/Public/GetCheckInListAttendeesPublicHandler.php b/backend/app/Services/Handlers/CheckInList/Public/GetCheckInListAttendeesPublicHandler.php index de6332ea0b..f1b1b6ae9f 100644 --- a/backend/app/Services/Handlers/CheckInList/Public/GetCheckInListAttendeesPublicHandler.php +++ b/backend/app/Services/Handlers/CheckInList/Public/GetCheckInListAttendeesPublicHandler.php @@ -5,7 +5,7 @@ use HiEvents\DomainObjects\CheckInListDomainObject; use HiEvents\DomainObjects\EventDomainObject; use HiEvents\DomainObjects\Generated\CheckInListDomainObjectAbstract; -use HiEvents\DomainObjects\TicketDomainObject; +use HiEvents\DomainObjects\ProductDomainObject; use HiEvents\Exceptions\CannotCheckInException; use HiEvents\Helper\DateHelper; use HiEvents\Http\DTO\QueryParamsDTO; @@ -30,7 +30,7 @@ public function __construct( public function handle(string $shortId, QueryParamsDTO $queryParams): Paginator { $checkInList = $this->checkInListRepository - ->loadRelation(TicketDomainObject::class) + ->loadRelation(ProductDomainObject::class) ->loadRelation(new Relationship(EventDomainObject::class, name: 'event')) ->findFirstWhere([ CheckInListDomainObjectAbstract::SHORT_ID => $shortId, diff --git a/backend/app/Services/Handlers/CheckInList/Public/GetCheckInListPublicHandler.php b/backend/app/Services/Handlers/CheckInList/Public/GetCheckInListPublicHandler.php index bbd24480a0..b773da1b9c 100644 --- a/backend/app/Services/Handlers/CheckInList/Public/GetCheckInListPublicHandler.php +++ b/backend/app/Services/Handlers/CheckInList/Public/GetCheckInListPublicHandler.php @@ -4,7 +4,7 @@ use HiEvents\DomainObjects\CheckInListDomainObject; use HiEvents\DomainObjects\EventDomainObject; -use HiEvents\DomainObjects\TicketDomainObject; +use HiEvents\DomainObjects\ProductDomainObject; use HiEvents\Repository\Eloquent\Value\Relationship; use HiEvents\Repository\Interfaces\CheckInListRepositoryInterface; use Symfony\Component\Routing\Exception\ResourceNotFoundException; @@ -21,7 +21,7 @@ public function handle(string $shortId): CheckInListDomainObject { $checkInList = $this->checkInListRepository ->loadRelation(new Relationship(domainObject: EventDomainObject::class, name: 'event')) - ->loadRelation(TicketDomainObject::class) + ->loadRelation(ProductDomainObject::class) ->findFirstWhere([ 'short_id' => $shortId, ]); diff --git a/backend/app/Services/Handlers/CheckInList/UpdateCheckInlistHandler.php b/backend/app/Services/Handlers/CheckInList/UpdateCheckInlistHandler.php index 4430781712..0317a8bc9a 100644 --- a/backend/app/Services/Handlers/CheckInList/UpdateCheckInlistHandler.php +++ b/backend/app/Services/Handlers/CheckInList/UpdateCheckInlistHandler.php @@ -4,7 +4,7 @@ use HiEvents\DomainObjects\CheckInListDomainObject; use HiEvents\Services\Domain\CheckInList\UpdateCheckInListService; -use HiEvents\Services\Domain\Ticket\Exception\UnrecognizedTicketIdException; +use HiEvents\Services\Domain\Product\Exception\UnrecognizedProductIdException; use HiEvents\Services\Handlers\CheckInList\DTO\UpsertCheckInListDTO; class UpdateCheckInlistHandler @@ -16,7 +16,7 @@ public function __construct( } /** - * @throws UnrecognizedTicketIdException + * @throws UnrecognizedProductIdException */ public function handle(UpsertCheckInListDTO $data): CheckInListDomainObject { @@ -30,7 +30,7 @@ public function handle(UpsertCheckInListDTO $data): CheckInListDomainObject return $this->updateCheckInlistService->updateCheckInlist( checkInList: $checkInList, - ticketIds: $data->ticketIds + productIds: $data->productIds ); } } diff --git a/backend/app/Services/Handlers/Event/DTO/EventStatsResponseDTO.php b/backend/app/Services/Handlers/Event/DTO/EventStatsResponseDTO.php index 534939473b..f6cf90d554 100644 --- a/backend/app/Services/Handlers/Event/DTO/EventStatsResponseDTO.php +++ b/backend/app/Services/Handlers/Event/DTO/EventStatsResponseDTO.php @@ -18,12 +18,15 @@ public function __construct( public EventCheckInStatsResponseDTO $check_in_stats, - public int $total_tickets_sold, + public int $total_products_sold, + public int $total_attendees_registered, + public int $total_orders, public float $total_gross_sales, public float $total_fees, public float $total_tax, public float $total_views, + public float $total_refunded, ) { } diff --git a/backend/app/Services/Handlers/Event/DuplicateEventHandler.php b/backend/app/Services/Handlers/Event/DuplicateEventHandler.php index 1ebc2e20c2..081cfdb49c 100644 --- a/backend/app/Services/Handlers/Event/DuplicateEventHandler.php +++ b/backend/app/Services/Handlers/Event/DuplicateEventHandler.php @@ -25,7 +25,7 @@ public function handle(DuplicateEventDataDTO $data): EventDomainObject accountId: $data->accountId, title: $data->title, startDate: $data->startDate, - duplicateTickets: $data->duplicateTickets, + duplicateProducts: $data->duplicateProducts, duplicateQuestions: $data->duplicateQuestions, duplicateSettings: $data->duplicateSettings, duplicatePromoCodes: $data->duplicatePromoCodes, diff --git a/backend/app/Services/Handlers/Event/GetPublicEventHandler.php b/backend/app/Services/Handlers/Event/GetPublicEventHandler.php index 888f6c9227..f52038e667 100644 --- a/backend/app/Services/Handlers/Event/GetPublicEventHandler.php +++ b/backend/app/Services/Handlers/Event/GetPublicEventHandler.php @@ -7,14 +7,15 @@ use HiEvents\DomainObjects\Generated\PromoCodeDomainObjectAbstract; use HiEvents\DomainObjects\ImageDomainObject; use HiEvents\DomainObjects\OrganizerDomainObject; +use HiEvents\DomainObjects\ProductCategoryDomainObject; +use HiEvents\DomainObjects\ProductDomainObject; +use HiEvents\DomainObjects\ProductPriceDomainObject; use HiEvents\DomainObjects\TaxAndFeesDomainObject; -use HiEvents\DomainObjects\TicketDomainObject; -use HiEvents\DomainObjects\TicketPriceDomainObject; use HiEvents\Repository\Eloquent\Value\Relationship; use HiEvents\Repository\Interfaces\EventRepositoryInterface; use HiEvents\Repository\Interfaces\PromoCodeRepositoryInterface; use HiEvents\Services\Domain\Event\EventPageViewIncrementService; -use HiEvents\Services\Domain\Ticket\TicketFilterService; +use HiEvents\Services\Domain\Product\ProductFilterService; use HiEvents\Services\Handlers\Event\DTO\GetPublicEventDTO; class GetPublicEventHandler @@ -22,7 +23,7 @@ class GetPublicEventHandler public function __construct( private readonly EventRepositoryInterface $eventRepository, private readonly PromoCodeRepositoryInterface $promoCodeRepository, - private readonly TicketFilterService $ticketFilterService, + private readonly ProductFilterService $productFilterService, private readonly EventPageViewIncrementService $eventPageViewIncrementService, ) { @@ -32,9 +33,11 @@ public function handle(GetPublicEventDTO $data): EventDomainObject { $event = $this->eventRepository ->loadRelation( - new Relationship(TicketDomainObject::class, [ - new Relationship(TicketPriceDomainObject::class), - new Relationship(TaxAndFeesDomainObject::class) + new Relationship(ProductCategoryDomainObject::class, [ + new Relationship(ProductDomainObject::class, [ + new Relationship(ProductPriceDomainObject::class), + new Relationship(TaxAndFeesDomainObject::class), + ]), ]) ) ->loadRelation(new Relationship(EventSettingDomainObject::class)) @@ -55,6 +58,9 @@ public function handle(GetPublicEventDTO $data): EventDomainObject $this->eventPageViewIncrementService->increment($data->eventId, $data->ipAddress); } - return $event->setTickets($this->ticketFilterService->filter($event->getTickets(), $promoCodeDomainObject)); + return $event->setProducts($this->productFilterService->filter( + productsCategories: $event->getProductCategories(), + promoCode: $promoCodeDomainObject + )); } } diff --git a/backend/app/Services/Handlers/Message/DTO/SendMessageDTO.php b/backend/app/Services/Handlers/Message/DTO/SendMessageDTO.php index 72619fe946..1da3b7b09e 100644 --- a/backend/app/Services/Handlers/Message/DTO/SendMessageDTO.php +++ b/backend/app/Services/Handlers/Message/DTO/SendMessageDTO.php @@ -19,7 +19,7 @@ public function __construct( public readonly ?int $order_id, public readonly ?int $id = null, public readonly ?array $attendee_ids = [], - public readonly ?array $ticket_ids = [], + public readonly ?array $product_ids = [], ) { diff --git a/backend/app/Services/Handlers/Message/SendMessageHandler.php b/backend/app/Services/Handlers/Message/SendMessageHandler.php index 5cd0387d85..ab04e7e66c 100644 --- a/backend/app/Services/Handlers/Message/SendMessageHandler.php +++ b/backend/app/Services/Handlers/Message/SendMessageHandler.php @@ -12,7 +12,7 @@ use HiEvents\Repository\Interfaces\AttendeeRepositoryInterface; use HiEvents\Repository\Interfaces\MessageRepositoryInterface; use HiEvents\Repository\Interfaces\OrderRepositoryInterface; -use HiEvents\Repository\Interfaces\TicketRepositoryInterface; +use HiEvents\Repository\Interfaces\ProductRepositoryInterface; use HiEvents\Services\Handlers\Message\DTO\SendMessageDTO; use HTMLPurifier; use Illuminate\Support\Collection; @@ -22,7 +22,7 @@ public function __construct( private OrderRepositoryInterface $orderRepository, private AttendeeRepositoryInterface $attendeeRepository, - private TicketRepositoryInterface $ticketRepository, + private ProductRepositoryInterface $productRepository, private MessageRepositoryInterface $messageRepository, private AccountRepositoryInterface $accountRepository, private HTMLPurifier $purifier, @@ -48,7 +48,7 @@ public function handle(SendMessageDTO $messageData): MessageDomainObject 'type' => $messageData->type->name, 'order_id' => $this->getOrderId($messageData), 'attendee_ids' => $this->getAttendeeIds($messageData)->toArray(), - 'ticket_ids' => $this->getTicketIds($messageData)->toArray(), + 'product_ids' => $this->getProductIds($messageData)->toArray(), 'sent_at' => Carbon::now()->toDateTimeString(), 'sent_by_user_id' => $messageData->sent_by_user_id, 'status' => MessageStatus::PROCESSING->name, @@ -63,7 +63,7 @@ public function handle(SendMessageDTO $messageData): MessageDomainObject 'is_test' => $messageData->is_test, 'order_id' => $message->getOrderId(), 'attendee_ids' => $message->getAttendeeIds(), - 'ticket_ids' => $message->getTicketIds(), + 'product_ids' => $message->getProductIds(), 'send_copy_to_current_user' => $messageData->send_copy_to_current_user, 'sent_by_user_id' => $messageData->sent_by_user_id, 'account_id' => $messageData->account_id, @@ -90,18 +90,18 @@ private function getAttendeeIds(SendMessageDTO $messageData): Collection } - private function getTicketIds(SendMessageDTO $messageData): Collection + private function getProductIds(SendMessageDTO $messageData): Collection { - $tickets = $this->ticketRepository->findWhereIn( + $products = $this->productRepository->findWhereIn( field: 'id', - values: $messageData->ticket_ids, + values: $messageData->product_ids, additionalWhere: [ 'event_id' => $messageData->event_id, ], columns: ['id'] ); - return $tickets->map(fn($attendee) => $attendee->getId()); + return $products->map(fn($attendee) => $attendee->getId()); } private function getOrderId(SendMessageDTO $messageData): ?int diff --git a/backend/app/Services/Handlers/Order/CompleteOrderHandler.php b/backend/app/Services/Handlers/Order/CompleteOrderHandler.php index 0d9751cfc9..7a17de9342 100644 --- a/backend/app/Services/Handlers/Order/CompleteOrderHandler.php +++ b/backend/app/Services/Handlers/Order/CompleteOrderHandler.php @@ -7,29 +7,31 @@ use Carbon\Carbon; use Exception; use HiEvents\DomainObjects\AttendeeDomainObject; +use HiEvents\DomainObjects\Enums\ProductType; use HiEvents\DomainObjects\Generated\AttendeeDomainObjectAbstract; use HiEvents\DomainObjects\Generated\OrderDomainObjectAbstract; -use HiEvents\DomainObjects\Generated\TicketPriceDomainObjectAbstract; +use HiEvents\DomainObjects\Generated\ProductPriceDomainObjectAbstract; use HiEvents\DomainObjects\OrderDomainObject; use HiEvents\DomainObjects\OrderItemDomainObject; +use HiEvents\DomainObjects\ProductDomainObject; +use HiEvents\DomainObjects\ProductPriceDomainObject; use HiEvents\DomainObjects\Status\AttendeeStatus; use HiEvents\DomainObjects\Status\OrderPaymentStatus; use HiEvents\DomainObjects\Status\OrderStatus; -use HiEvents\DomainObjects\TicketDomainObject; -use HiEvents\DomainObjects\TicketPriceDomainObject; use HiEvents\Events\OrderStatusChangedEvent; use HiEvents\Exceptions\ResourceConflictException; use HiEvents\Helper\IdHelper; use HiEvents\Repository\Eloquent\Value\Relationship; use HiEvents\Repository\Interfaces\AttendeeRepositoryInterface; use HiEvents\Repository\Interfaces\OrderRepositoryInterface; +use HiEvents\Repository\Interfaces\ProductPriceRepositoryInterface; use HiEvents\Repository\Interfaces\QuestionAnswerRepositoryInterface; -use HiEvents\Repository\Interfaces\TicketPriceRepositoryInterface; use HiEvents\Services\Domain\Payment\Stripe\EventHandlers\PaymentIntentSucceededHandler; -use HiEvents\Services\Domain\Ticket\TicketQuantityUpdateService; -use HiEvents\Services\Handlers\Order\DTO\CompleteOrderAttendeeDTO; +use HiEvents\Services\Domain\Product\ProductQuantityUpdateService; use HiEvents\Services\Handlers\Order\DTO\CompleteOrderDTO; use HiEvents\Services\Handlers\Order\DTO\CompleteOrderOrderDTO; +use HiEvents\Services\Handlers\Order\DTO\CompleteOrderProductDataDTO; +use HiEvents\Services\Handlers\Order\DTO\CreatedProductDataDTO; use HiEvents\Services\Handlers\Order\DTO\OrderQuestionsDTO; use Illuminate\Support\Collection; use Illuminate\Support\Facades\DB; @@ -45,8 +47,8 @@ public function __construct( private readonly OrderRepositoryInterface $orderRepository, private readonly AttendeeRepositoryInterface $attendeeRepository, private readonly QuestionAnswerRepositoryInterface $questionAnswersRepository, - private readonly TicketQuantityUpdateService $ticketQuantityUpdateService, - private readonly TicketPriceRepositoryInterface $ticketPriceRepository, + private readonly ProductQuantityUpdateService $productQuantityUpdateService, + private readonly ProductPriceRepositoryInterface $productPriceRepository, ) { } @@ -63,20 +65,20 @@ public function handle(string $orderShortId, CompleteOrderDTO $orderData): Order $updatedOrder = $this->updateOrder($order, $orderDTO); - $this->createAttendees($orderData->attendees, $order); + $this->createAttendees($orderData->products, $order); if ($orderData->order->questions) { $this->createOrderQuestions($orderDTO->questions, $order); } /** - * If there's no payment required, immediately update the ticket quantities, otherwise handle + * If there's no payment required, immediately update the product quantities, otherwise handle * this in the PaymentIntentEventHandlerService * * @see PaymentIntentSucceededHandler */ if (!$order->isPaymentRequired()) { - $this->ticketQuantityUpdateService->updateQuantitiesFromOrder($updatedOrder); + $this->productQuantityUpdateService->updateQuantitiesFromOrder($updatedOrder); } OrderStatusChangedEvent::dispatch($updatedOrder); @@ -86,49 +88,69 @@ public function handle(string $orderShortId, CompleteOrderDTO $orderData): Order } /** + * @param Collection $orderProducts * @throws Exception */ - private function createAttendees(Collection $attendees, OrderDomainObject $order): void + private function createAttendees(Collection $orderProducts, OrderDomainObject $order): void { $inserts = []; + $createdProductData = collect(); - $ticketsPrices = $this->ticketPriceRepository->findWhereIn( - field: TicketPriceDomainObjectAbstract::ID, - values: $attendees->pluck('ticket_price_id')->toArray(), + $productsPrices = $this->productPriceRepository->findWhereIn( + field: ProductPriceDomainObjectAbstract::ID, + values: $orderProducts->pluck('product_price_id')->toArray(), ); - $this->validateTicketPriceIdsMatchOrder($order, $ticketsPrices); - $this->validateAttendees($order, $attendees); + $this->validateProductPriceIdsMatchOrder($order, $productsPrices); + $this->validateTicketProductsCount($order, $orderProducts); - foreach ($attendees as $attendee) { - $ticketId = $ticketsPrices->first( - fn(TicketPriceDomainObject $ticketPrice) => $ticketPrice->getId() === $attendee->ticket_price_id) - ->getTicketId(); + foreach ($orderProducts as $attendee) { + $productId = $productsPrices->first( + fn(ProductPriceDomainObject $productPrice) => $productPrice->getId() === $attendee->product_price_id) + ->getProductId(); + $productType = $this->getProductTypeFromPriceId($attendee->product_price_id, $order->getOrderItems()); + + // If it's not a ticket, skip, as we only want to create attendees for tickets + if ($productType !== ProductType::TICKET->name) { + $createdProductData->push(new CreatedProductDataDTO( + productRequestData: $attendee, + shortId: null, + )); + + continue; + } + + $shortId = IdHelper::shortId(IdHelper::ATTENDEE_PREFIX); $inserts[] = [ AttendeeDomainObjectAbstract::EVENT_ID => $order->getEventId(), - AttendeeDomainObjectAbstract::TICKET_ID => $ticketId, - AttendeeDomainObjectAbstract::TICKET_PRICE_ID => $attendee->ticket_price_id, + AttendeeDomainObjectAbstract::PRODUCT_ID => $productId, + AttendeeDomainObjectAbstract::PRODUCT_PRICE_ID => $attendee->product_price_id, AttendeeDomainObjectAbstract::STATUS => AttendeeStatus::ACTIVE->name, AttendeeDomainObjectAbstract::EMAIL => $attendee->email, AttendeeDomainObjectAbstract::FIRST_NAME => $attendee->first_name, AttendeeDomainObjectAbstract::LAST_NAME => $attendee->last_name, AttendeeDomainObjectAbstract::ORDER_ID => $order->getId(), AttendeeDomainObjectAbstract::PUBLIC_ID => IdHelper::publicId(IdHelper::ATTENDEE_PREFIX), - AttendeeDomainObjectAbstract::SHORT_ID => IdHelper::shortId(IdHelper::ATTENDEE_PREFIX), + AttendeeDomainObjectAbstract::SHORT_ID => $shortId, AttendeeDomainObjectAbstract::LOCALE => $order->getLocale(), ]; + + $createdProductData->push(new CreatedProductDataDTO( + productRequestData: $attendee, + shortId: $shortId, + )); } if (!$this->attendeeRepository->insert($inserts)) { throw new RuntimeException(__('Failed to create attendee')); } - $insertedAttendees = $this->attendeeRepository->findWhere([ - AttendeeDomainObjectAbstract::ORDER_ID => $order->getId() - ]); - - $this->createAttendeeQuestions($attendees, $insertedAttendees, $order, $ticketsPrices); + $this->createProductQuestions( + createdAttendees: $createdProductData, + order: $order, + productPrices: $productsPrices, + ); } private function createOrderQuestions(Collection $questions, OrderDomainObject $order): void @@ -145,32 +167,39 @@ private function createOrderQuestions(Collection $questions, OrderDomainObject $ }); } - private function createAttendeeQuestions( - Collection $attendees, - Collection $insertedAttendees, + /** + * @param Collection $createdAttendees + * @param Collection $productPrices + * @throws ResourceConflictException|Exception + */ + private function createProductQuestions( + Collection $createdAttendees, OrderDomainObject $order, - Collection $ticketPrices, + Collection $productPrices ): void { - $insertedIds = []; - /** @var CompleteOrderAttendeeDTO $attendee */ - foreach ($attendees as $attendee) { - $ticketId = $ticketPrices->first( - fn(TicketPriceDomainObject $ticketPrice) => $ticketPrice->getId() === $attendee->ticket_price_id) - ->getTicketId(); - - $attendeeIterator = $insertedAttendees->filter( - fn(AttendeeDomainObject $insertedAttendee) => $insertedAttendee->getTicketId() === $ticketId - && !in_array($insertedAttendee->getId(), $insertedIds, true) - )->getIterator(); - - if ($attendee->questions === null) { + $newAttendees = $this->attendeeRepository->findWhereIn( + field: AttendeeDomainObjectAbstract::SHORT_ID, + values: $createdAttendees->pluck('shortId')->toArray(), + ); + + foreach ($createdAttendees as $createdAttendee) { + $productRequestData = $createdAttendee->productRequestData; + + if ($productRequestData->questions === null) { continue; } - foreach ($attendee->questions as $question) { - $attendeeId = $attendeeIterator->current()->getId(); + $productId = $productPrices->first( + fn(ProductPriceDomainObject $productPrice) => $productPrice->getId() === $productRequestData->product_price_id + )->getProductId(); + + // This will be null for non-ticket products + $insertedAttendee = $newAttendees->first( + fn(AttendeeDomainObject $attendee) => $attendee->getShortId() === $createdAttendee->shortId, + ); + foreach ($productRequestData->questions as $question) { if (empty($question->response)) { continue; } @@ -179,11 +208,9 @@ private function createAttendeeQuestions( 'question_id' => $question->question_id, 'answer' => $question->response['answer'] ?? $question->response, 'order_id' => $order->getId(), - 'ticket_id' => $ticketId, - 'attendee_id' => $attendeeId + 'product_id' => $productId, + 'attendee_id' => $insertedAttendee?->getId(), ]); - - $insertedIds[] = $attendeeId; } } } @@ -215,7 +242,7 @@ private function getOrder(string $orderShortId): OrderDomainObject ->loadRelation( new Relationship( domainObject: OrderItemDomainObject::class, - nested: [new Relationship(TicketDomainObject::class, name: 'ticket')] + nested: [new Relationship(ProductDomainObject::class, name: 'product')] )) ->findByShortId($orderShortId); @@ -249,33 +276,49 @@ private function updateOrder(OrderDomainObject $order, CompleteOrderOrderDTO $or } /** - * Check if the passed ticket price IDs match what exist in the order_items table + * Check if the passed product price IDs match what exist in the order_items table * * @throws ResourceConflictException */ - private function validateTicketPriceIdsMatchOrder(OrderDomainObject $order, Collection $ticketsPrices): void + private function validateProductPriceIdsMatchOrder(OrderDomainObject $order, Collection $productsPrices): void { - $orderTicketPriceIds = $order->getOrderItems() - ?->map(fn(OrderItemDomainObject $orderItem) => $orderItem->getTicketPriceId())->toArray(); + $orderProductPriceIds = $order->getOrderItems() + ?->map(fn(OrderItemDomainObject $orderItem) => $orderItem->getProductPriceId())->toArray(); - $ticketsPricesIds = $ticketsPrices->map(fn(TicketPriceDomainObject $ticketPrice) => $ticketPrice->getId()); + $productsPricesIds = $productsPrices->map(fn(ProductPriceDomainObject $productPrice) => $productPrice->getId()); - if ($ticketsPricesIds->diff($orderTicketPriceIds)->isNotEmpty()) { - throw new ResourceConflictException(__('There is an unexpected ticket price ID in the order')); + if ($productsPricesIds->diff($orderProductPriceIds)->isNotEmpty()) { + throw new ResourceConflictException(__('There is an unexpected product price ID in the order')); } } /** * @throws ResourceConflictException */ - private function validateAttendees(OrderDomainObject $order, Collection $attendees): void + private function validateTicketProductsCount(OrderDomainObject $order, Collection $attendees): void { - $orderAttendeeCount = $order->getOrderItems()->sum(fn(OrderItemDomainObject $orderItem) => $orderItem->getQuantity()); - - if ($orderAttendeeCount !== $attendees->count()) { + $orderAttendeeCount = $order->getOrderItems() + ?->filter(fn(OrderItemDomainObject $orderItem) => $orderItem->getProductType() === ProductType::TICKET->name) + ?->sum(fn(OrderItemDomainObject $orderItem) => $orderItem->getQuantity()); + + $ticketAttendeeCount = $attendees + ->filter( + fn(CompleteOrderProductDataDTO $attendee) => $this->getProductTypeFromPriceId( + $attendee->product_price_id, + $order->getOrderItems() + ) === ProductType::TICKET->name) + ->count(); + + if ($orderAttendeeCount !== $ticketAttendeeCount) { throw new ResourceConflictException( __('The number of attendees does not match the number of tickets in the order') ); } } + + private function getProductTypeFromPriceId(int $priceId, Collection $orderItems): string + { + return $orderItems->first(fn(OrderItemDomainObject $orderItem) => $orderItem->getProductPriceId() === $priceId) + ->getProductType(); + } } diff --git a/backend/app/Services/Handlers/Order/CreateOrderHandler.php b/backend/app/Services/Handlers/Order/CreateOrderHandler.php index ba63055e7e..80158bc7e3 100644 --- a/backend/app/Services/Handlers/Order/CreateOrderHandler.php +++ b/backend/app/Services/Handlers/Order/CreateOrderHandler.php @@ -64,7 +64,7 @@ public function handle( $orderItems = $this->orderItemProcessingService->process( order: $order, - ticketsOrderDetails: $createOrderPublicDTO->tickets, + productsOrderDetails: $createOrderPublicDTO->products, event: $event, promoCode: $promoCode, ); diff --git a/backend/app/Services/Handlers/Order/DTO/CompleteOrderAttendeeDTO.php b/backend/app/Services/Handlers/Order/DTO/CompleteOrderAttendeeDTO.php deleted file mode 100644 index 9d7bb32b14..0000000000 --- a/backend/app/Services/Handlers/Order/DTO/CompleteOrderAttendeeDTO.php +++ /dev/null @@ -1,21 +0,0 @@ - $attendees + * @param Collection $products */ public function __construct( public CompleteOrderOrderDTO $order, - #[CollectionOf(CompleteOrderAttendeeDTO::class)] - public Collection $attendees + #[CollectionOf(CompleteOrderProductDataDTO::class)] + public Collection $products ) { } diff --git a/backend/app/Services/Handlers/Order/DTO/CompleteOrderProductDataDTO.php b/backend/app/Services/Handlers/Order/DTO/CompleteOrderProductDataDTO.php new file mode 100644 index 0000000000..a624feb7b8 --- /dev/null +++ b/backend/app/Services/Handlers/Order/DTO/CompleteOrderProductDataDTO.php @@ -0,0 +1,30 @@ +first_name !== null + && $this->last_name !== null + && $this->email !== null; + } +} diff --git a/backend/app/Services/Handlers/Order/DTO/CreateOrderPublicDTO.php b/backend/app/Services/Handlers/Order/DTO/CreateOrderPublicDTO.php index 17bf413dbb..e24ed402bb 100644 --- a/backend/app/Services/Handlers/Order/DTO/CreateOrderPublicDTO.php +++ b/backend/app/Services/Handlers/Order/DTO/CreateOrderPublicDTO.php @@ -9,9 +9,9 @@ class CreateOrderPublicDTO extends BaseDTO { public function __construct( /** - * @var Collection + * @var Collection */ - public readonly Collection $tickets, + public readonly Collection $products, public readonly bool $is_user_authenticated, public readonly string $session_identifier, public readonly ?string $order_locale = null, diff --git a/backend/app/Services/Handlers/Order/DTO/CreatedProductDataDTO.php b/backend/app/Services/Handlers/Order/DTO/CreatedProductDataDTO.php new file mode 100644 index 0000000000..9a113745e6 --- /dev/null +++ b/backend/app/Services/Handlers/Order/DTO/CreatedProductDataDTO.php @@ -0,0 +1,15 @@ +prices->map(fn(ProductPriceDTO $price) => ProductPriceDomainObject::hydrateFromArray([ + ProductPriceDomainObjectAbstract::PRICE => $productsData->type === ProductPriceType::FREE ? 0.00 : $price->price, + ProductPriceDomainObjectAbstract::LABEL => $price->label, + ProductPriceDomainObjectAbstract::SALE_START_DATE => $price->sale_start_date, + ProductPriceDomainObjectAbstract::SALE_END_DATE => $price->sale_end_date, + ProductPriceDomainObjectAbstract::INITIAL_QUANTITY_AVAILABLE => $price->initial_quantity_available, + ProductPriceDomainObjectAbstract::IS_HIDDEN => $price->is_hidden, + ])); + + $category = $this->getProductCategoryService->getCategory( + categoryId: $productsData->product_category_id, + eventId: $productsData->event_id + ); + + return $this->productCreateService->createProduct( + product: (new ProductDomainObject()) + ->setTitle($productsData->title) + ->setType($productsData->type->name) + ->setOrder($productsData->order) + ->setSaleStartDate($productsData->sale_start_date) + ->setSaleEndDate($productsData->sale_end_date) + ->setMaxPerOrder($productsData->max_per_order) + ->setDescription($productsData->description) + ->setMinPerOrder($productsData->min_per_order) + ->setIsHidden($productsData->is_hidden) + ->setStartCollapsed($productsData->start_collapsed) + ->setHideBeforeSaleStartDate($productsData->hide_before_sale_start_date) + ->setHideAfterSaleEndDate($productsData->hide_after_sale_end_date) + ->setHideWhenSoldOut($productsData->hide_when_sold_out) + ->setShowQuantityRemaining($productsData->show_quantity_remaining) + ->setIsHiddenWithoutPromoCode($productsData->is_hidden_without_promo_code) + ->setProductPrices($productPrices) + ->setEventId($productsData->event_id) + ->setProductType($productsData->product_type->name) + ->setProductCategoryId($category->getId()), + accountId: $productsData->account_id, + taxAndFeeIds: $productsData->tax_and_fee_ids, + ); + } +} diff --git a/backend/app/Services/Handlers/Product/DTO/UpsertProductDTO.php b/backend/app/Services/Handlers/Product/DTO/UpsertProductDTO.php new file mode 100644 index 0000000000..bcf6308c4b --- /dev/null +++ b/backend/app/Services/Handlers/Product/DTO/UpsertProductDTO.php @@ -0,0 +1,45 @@ +deleteProductService->deleteProduct($productId, $eventId); + } +} diff --git a/backend/app/Services/Handlers/Product/EditProductHandler.php b/backend/app/Services/Handlers/Product/EditProductHandler.php new file mode 100644 index 0000000000..38048e42a5 --- /dev/null +++ b/backend/app/Services/Handlers/Product/EditProductHandler.php @@ -0,0 +1,153 @@ +databaseManager->transaction(function () use ($productsData) { + $where = [ + 'event_id' => $productsData->event_id, + 'id' => $productsData->product_id, + ]; + + $product = $this->updateProduct($productsData, $where); + + $this->addTaxes($product, $productsData); + + $this->priceUpdateService->updatePrices( + $product, + $productsData, + $product->getProductPrices(), + $this->eventRepository->findById($productsData->event_id) + ); + + return $this->productRepository + ->loadRelation(ProductPriceDomainObject::class) + ->findById($product->getId()); + }); + } + + /** + * @throws CannotChangeProductTypeException + */ + private function updateProduct(UpsertProductDTO $productsData, array $where): ProductDomainObject + { + $event = $this->eventRepository->findById($productsData->event_id); + + $this->validateChangeInProductType($productsData); + + $productCategory = $this->getProductCategoryService->getCategory( + $productsData->product_category_id, + $productsData->event_id, + ); + + $this->productRepository->updateWhere( + attributes: [ + 'title' => $productsData->title, + 'type' => $productsData->type->name, + 'order' => $this->productOrderingService->getOrderForNewProduct( + eventId: $productsData->event_id, + productCategoryId: $productCategory->getId(), + ), + 'sale_start_date' => $productsData->sale_start_date + ? DateHelper::convertToUTC($productsData->sale_start_date, $event->getTimezone()) + : null, + 'sale_end_date' => $productsData->sale_end_date + ? DateHelper::convertToUTC($productsData->sale_end_date, $event->getTimezone()) + : null, + 'max_per_order' => $productsData->max_per_order, + 'description' => $this->purifier->purify($productsData->description), + 'min_per_order' => $productsData->min_per_order, + 'is_hidden' => $productsData->is_hidden, + 'start_collapsed' => $productsData->start_collapsed, + 'hide_before_sale_start_date' => $productsData->hide_before_sale_start_date, + 'hide_after_sale_end_date' => $productsData->hide_after_sale_end_date, + 'hide_when_sold_out' => $productsData->hide_when_sold_out, + 'show_quantity_remaining' => $productsData->show_quantity_remaining, + 'is_hidden_without_promo_code' => $productsData->is_hidden_without_promo_code, + 'product_type' => $productsData->product_type->name, + 'product_category_id' => $productCategory->getId(), + ], + where: $where + ); + + return $this->productRepository + ->loadRelation(ProductPriceDomainObject::class) + ->findFirstWhere($where); + } + + /** + * @throws Exception + */ + private function addTaxes(ProductDomainObject $product, UpsertProductDTO $productsData): void + { + $this->taxAndProductAssociationService->addTaxesToProduct( + new TaxAndProductAssociateParams( + productId: $product->getId(), + accountId: $productsData->account_id, + taxAndFeeIds: $productsData->tax_and_fee_ids, + ) + ); + } + + /** + * @throws CannotChangeProductTypeException + * @todo - We should probably check reserved products here as well + */ + private function validateChangeInProductType(UpsertProductDTO $productsData): void + { + $product = $this->productRepository + ->loadRelation(ProductPriceDomainObject::class) + ->findById($productsData->product_id); + + $quantitySold = $product->getProductPrices() + ->sum(fn(ProductPriceDomainObject $price) => $price->getQuantitySold()); + + if ($product->getType() !== $productsData->type->name && $quantitySold > 0) { + throw new CannotChangeProductTypeException( + __('Product type cannot be changed as products have been registered for this type') + ); + } + } +} diff --git a/backend/app/Services/Handlers/Product/GetProductsHandler.php b/backend/app/Services/Handlers/Product/GetProductsHandler.php new file mode 100644 index 0000000000..4bd2225864 --- /dev/null +++ b/backend/app/Services/Handlers/Product/GetProductsHandler.php @@ -0,0 +1,37 @@ +productRepository + ->loadRelation(ProductPriceDomainObject::class) + ->loadRelation(TaxAndFeesDomainObject::class) + ->findByEventId($eventId, $queryParamsDTO); + + $filteredProducts = $this->productFilterService->filter( + productsCategories: $productPaginator->getCollection(), + hideSoldOutProducts: false, + ); + + $productPaginator->setCollection($filteredProducts); + + return $productPaginator; + } +} diff --git a/backend/app/Services/Handlers/Product/SortProductsHandler.php b/backend/app/Services/Handlers/Product/SortProductsHandler.php new file mode 100644 index 0000000000..0a7fa836fc --- /dev/null +++ b/backend/app/Services/Handlers/Product/SortProductsHandler.php @@ -0,0 +1,73 @@ +productCategoryRepository + ->loadRelation(ProductDomainObject::class) + ->findWhere(['event_id' => $eventId]); + + $existingCategoryIds = $categories->map(fn($category) => $category->getId())->toArray(); + $existingProductIds = $categories->flatMap(fn($category) => $category->products->map(fn($product) => $product->getId()))->toArray(); + + $orderedCategoryIds = collect($sortData)->pluck('product_category_id')->toArray(); + $orderedProductIds = collect($sortData) + ->flatMap(fn($category) => collect($category['sorted_products'])->pluck('id')) + ->toArray(); + + if (array_diff($existingCategoryIds, $orderedCategoryIds) || array_diff($orderedCategoryIds, $existingCategoryIds)) { + throw new ResourceConflictException( + __('The ordered category IDs must exactly match all categories for the event without missing or extra IDs.') + ); + } + + if (array_diff($existingProductIds, $orderedProductIds) || array_diff($orderedProductIds, $existingProductIds)) { + throw new ResourceConflictException( + __('The ordered product IDs must exactly match all products for the event without missing or extra IDs.') + ); + } + + $productUpdates = []; + $categoryUpdates = []; + + foreach ($sortData as $categoryIndex => $category) { + $categoryId = $category['product_category_id']; + $categoryUpdates[] = [ + 'id' => $categoryId, + 'order' => $categoryIndex + 1, + ]; + + foreach ($category['sorted_products'] as $productIndex => $product) { + $productUpdates[] = [ + 'id' => $product['id'], + 'order' => $productIndex + 1, + 'product_category_id' => $categoryId, + ]; + } + } + + $this->productRepository->bulkUpdateProductsAndCategories( + eventId: $eventId, + productUpdates: $productUpdates, + categoryUpdates: $categoryUpdates, + ); + } +} diff --git a/backend/app/Services/Handlers/ProductCategory/CreateProductCategoryHandler.php b/backend/app/Services/Handlers/ProductCategory/CreateProductCategoryHandler.php new file mode 100644 index 0000000000..4c1523cb85 --- /dev/null +++ b/backend/app/Services/Handlers/ProductCategory/CreateProductCategoryHandler.php @@ -0,0 +1,27 @@ +productCategoryService->createCategory( + name: $dto->name, + isHidden: $dto->is_hidden, + eventId: $dto->event_id, + description: $dto->description, + noProductsMessage: $dto->no_products_message, + ); + } +} diff --git a/backend/app/Services/Handlers/ProductCategory/DTO/UpsertProductCategoryDTO.php b/backend/app/Services/Handlers/ProductCategory/DTO/UpsertProductCategoryDTO.php new file mode 100644 index 0000000000..783f162f33 --- /dev/null +++ b/backend/app/Services/Handlers/ProductCategory/DTO/UpsertProductCategoryDTO.php @@ -0,0 +1,19 @@ +deleteProductCategoryService->deleteProductCategory($productCategoryId, $eventId); + } +} diff --git a/backend/app/Services/Handlers/ProductCategory/EditProductCategoryHandler.php b/backend/app/Services/Handlers/ProductCategory/EditProductCategoryHandler.php new file mode 100644 index 0000000000..04dbb3a87e --- /dev/null +++ b/backend/app/Services/Handlers/ProductCategory/EditProductCategoryHandler.php @@ -0,0 +1,34 @@ +productCategoryRepository->updateWhere( + attributes: [ + 'name' => $dto->name, + 'is_hidden' => $dto->is_hidden, + 'description' => $dto->description, + 'no_products_message' => $dto->no_products_message, + ], + where: [ + 'id' => $dto->product_category_id, + 'event_id' => $dto->event_id, + ], + ); + + return $this->productCategoryRepository->findById($dto->product_category_id); + } +} diff --git a/backend/app/Services/Handlers/ProductCategory/GetProductCategoriesHandler.php b/backend/app/Services/Handlers/ProductCategory/GetProductCategoriesHandler.php new file mode 100644 index 0000000000..c9d8e8fd8f --- /dev/null +++ b/backend/app/Services/Handlers/ProductCategory/GetProductCategoriesHandler.php @@ -0,0 +1,50 @@ +productCategoryRepository + ->loadRelation(new Relationship( + domainObject: ProductDomainObject::class, + nested: [ + new Relationship(ProductPriceDomainObject::class), + new Relationship(TaxAndFeesDomainObject::class), + ], + orderAndDirections: [ + new OrderAndDirection( + order: ProductDomainObjectAbstract::ORDER, + ), + ], + )) + ->findWhere( + where: [ + 'event_id' => $eventId, + ], + orderAndDirections: [ + new OrderAndDirection( + order: ProductCategoryDomainObjectAbstract::ORDER, + ), + ], + ); + } +} diff --git a/backend/app/Services/Handlers/ProductCategory/GetProductCategoryHandler.php b/backend/app/Services/Handlers/ProductCategory/GetProductCategoryHandler.php new file mode 100644 index 0000000000..485443b7de --- /dev/null +++ b/backend/app/Services/Handlers/ProductCategory/GetProductCategoryHandler.php @@ -0,0 +1,44 @@ +productCategoryRepository + ->loadRelation(new Relationship( + domainObject: ProductDomainObject::class, + nested: [ + new Relationship(ProductPriceDomainObject::class), + new Relationship(TaxAndFeesDomainObject::class), + ], + orderAndDirections: [ + new OrderAndDirection( + order: ProductDomainObjectAbstract::ORDER, + ), + ], + )) + ->findFirstWhere( + where: [ + 'event_id' => $eventId, + 'id' => $productCategoryId, + ] + ); + } +} diff --git a/backend/app/Services/Handlers/PromoCode/CreatePromoCodeHandler.php b/backend/app/Services/Handlers/PromoCode/CreatePromoCodeHandler.php index 84e954a393..dac9da0528 100644 --- a/backend/app/Services/Handlers/PromoCode/CreatePromoCodeHandler.php +++ b/backend/app/Services/Handlers/PromoCode/CreatePromoCodeHandler.php @@ -5,7 +5,7 @@ use HiEvents\DomainObjects\PromoCodeDomainObject; use HiEvents\Exceptions\ResourceConflictException; use HiEvents\Services\Domain\PromoCode\CreatePromoCodeService; -use HiEvents\Services\Domain\Ticket\Exception\UnrecognizedTicketIdException; +use HiEvents\Services\Domain\Product\Exception\UnrecognizedProductIdException; use HiEvents\Services\Handlers\PromoCode\DTO\UpsertPromoCodeDTO; readonly class CreatePromoCodeHandler @@ -18,7 +18,7 @@ public function __construct( /** * @throws ResourceConflictException - * @throws UnrecognizedTicketIdException + * @throws UnrecognizedProductIdException */ public function handle(int $eventId, UpsertPromoCodeDTO $promoCodeDTO): PromoCodeDomainObject { @@ -30,7 +30,7 @@ public function handle(int $eventId, UpsertPromoCodeDTO $promoCodeDTO): PromoCod ->setDiscount($promoCodeDTO->discount) ->setExpiryDate($promoCodeDTO->expiry_date) ->setMaxAllowedUsages($promoCodeDTO->max_allowed_usages) - ->setApplicableTicketIds($promoCodeDTO->applicable_ticket_ids) + ->setApplicableProductIds($promoCodeDTO->applicable_product_ids) ); } } diff --git a/backend/app/Services/Handlers/PromoCode/DTO/UpsertPromoCodeDTO.php b/backend/app/Services/Handlers/PromoCode/DTO/UpsertPromoCodeDTO.php index 2ae5edfa4a..aa3976792a 100644 --- a/backend/app/Services/Handlers/PromoCode/DTO/UpsertPromoCodeDTO.php +++ b/backend/app/Services/Handlers/PromoCode/DTO/UpsertPromoCodeDTO.php @@ -9,7 +9,7 @@ class UpsertPromoCodeDTO public function __construct( public readonly string $code, public readonly int $event_id, - public readonly array $applicable_ticket_ids, + public readonly array $applicable_product_ids, public readonly PromoCodeDiscountTypeEnum $discount_type, public readonly ?float $discount, public readonly ?string $expiry_date, diff --git a/backend/app/Services/Handlers/PromoCode/UpdatePromoCodeHandler.php b/backend/app/Services/Handlers/PromoCode/UpdatePromoCodeHandler.php index 99197f9fbd..31ccddfe42 100644 --- a/backend/app/Services/Handlers/PromoCode/UpdatePromoCodeHandler.php +++ b/backend/app/Services/Handlers/PromoCode/UpdatePromoCodeHandler.php @@ -9,28 +9,28 @@ use HiEvents\Helper\DateHelper; use HiEvents\Repository\Interfaces\EventRepositoryInterface; use HiEvents\Repository\Interfaces\PromoCodeRepositoryInterface; -use HiEvents\Services\Domain\Ticket\EventTicketValidationService; -use HiEvents\Services\Domain\Ticket\Exception\UnrecognizedTicketIdException; +use HiEvents\Services\Domain\Product\EventProductValidationService; +use HiEvents\Services\Domain\Product\Exception\UnrecognizedProductIdException; use HiEvents\Services\Handlers\PromoCode\DTO\UpsertPromoCodeDTO; readonly class UpdatePromoCodeHandler { public function __construct( - private PromoCodeRepositoryInterface $promoCodeRepository, - private EventTicketValidationService $eventTicketValidationService, - private EventRepositoryInterface $eventRepository, + private PromoCodeRepositoryInterface $promoCodeRepository, + private EventProductValidationService $eventProductValidationService, + private EventRepositoryInterface $eventRepository, ) { } /** * @throws ResourceConflictException - * @throws UnrecognizedTicketIdException + * @throws UnrecognizedProductIdException */ public function handle(int $promoCodeId, UpsertPromoCodeDTO $promoCodeDTO): PromoCodeDomainObject { - $this->eventTicketValidationService->validateTicketIds( - ticketIds: $promoCodeDTO->applicable_ticket_ids, + $this->eventProductValidationService->validateProductIds( + productIds: $promoCodeDTO->applicable_product_ids, eventId: $promoCodeDTO->event_id ); @@ -57,7 +57,7 @@ public function handle(int $promoCodeId, UpsertPromoCodeDTO $promoCodeDTO): Prom ? DateHelper::convertToUTC($promoCodeDTO->expiry_date, $event->getTimezone()) : null, PromoCodeDomainObjectAbstract::MAX_ALLOWED_USAGES => $promoCodeDTO->max_allowed_usages, - PromoCodeDomainObjectAbstract::APPLICABLE_TICKET_IDS => $promoCodeDTO->applicable_ticket_ids, + PromoCodeDomainObjectAbstract::APPLICABLE_PRODUCT_IDS => $promoCodeDTO->applicable_product_ids, ]); } } diff --git a/backend/app/Services/Handlers/Question/CreateQuestionHandler.php b/backend/app/Services/Handlers/Question/CreateQuestionHandler.php index a8f641e768..bfa0c73df1 100644 --- a/backend/app/Services/Handlers/Question/CreateQuestionHandler.php +++ b/backend/app/Services/Handlers/Question/CreateQuestionHandler.php @@ -34,7 +34,7 @@ public function handle(UpsertQuestionDTO $createQuestionDTO): QuestionDomainObje return $this->createQuestionService->createQuestion( $question, - $createQuestionDTO->ticket_ids, + $createQuestionDTO->product_ids, ); } } diff --git a/backend/app/Services/Handlers/Question/DTO/UpsertQuestionDTO.php b/backend/app/Services/Handlers/Question/DTO/UpsertQuestionDTO.php index c10345406e..ffb76e271f 100644 --- a/backend/app/Services/Handlers/Question/DTO/UpsertQuestionDTO.php +++ b/backend/app/Services/Handlers/Question/DTO/UpsertQuestionDTO.php @@ -14,7 +14,7 @@ public function __construct( public bool $required, public ?array $options, public int $event_id, - public array $ticket_ids, + public array $product_ids, public bool $is_hidden, public QuestionBelongsTo $belongs_to, public ?string $description = null, diff --git a/backend/app/Services/Handlers/Question/EditQuestionHandler.php b/backend/app/Services/Handlers/Question/EditQuestionHandler.php index 3cfe83a15f..40c30be8ca 100644 --- a/backend/app/Services/Handlers/Question/EditQuestionHandler.php +++ b/backend/app/Services/Handlers/Question/EditQuestionHandler.php @@ -34,7 +34,7 @@ public function handle(int $questionId, UpsertQuestionDTO $createQuestionDTO): Q return $this->editQuestionService->editQuestion( question: $question, - ticketIds: $createQuestionDTO->ticket_ids, + productIds: $createQuestionDTO->product_ids, ); } } diff --git a/backend/app/Services/Handlers/Question/SortQuestionsHandler.php b/backend/app/Services/Handlers/Question/SortQuestionsHandler.php index 379271f9ef..bd3894d273 100644 --- a/backend/app/Services/Handlers/Question/SortQuestionsHandler.php +++ b/backend/app/Services/Handlers/Question/SortQuestionsHandler.php @@ -20,7 +20,7 @@ public function handle(int $eventId, array $data): void $questionIdResult = $this->questionRepository->findWhere([ 'event_id' => $eventId, ]) - ->map(fn($ticket) => $ticket->getId()) + ->map(fn($product) => $product->getId()) ->toArray(); $extraInOrdered = array_diff($orderedQuestionIds, $questionIdResult); diff --git a/backend/app/Services/Handlers/Ticket/CreateTicketHandler.php b/backend/app/Services/Handlers/Ticket/CreateTicketHandler.php deleted file mode 100644 index c055e65b2f..0000000000 --- a/backend/app/Services/Handlers/Ticket/CreateTicketHandler.php +++ /dev/null @@ -1,61 +0,0 @@ -prices->map(fn(TicketPriceDTO $price) => TicketPriceDomainObject::hydrateFromArray([ - TicketPriceDomainObjectAbstract::PRICE => $ticketsData->type === TicketType::FREE ? 0.00 : $price->price, - TicketPriceDomainObjectAbstract::LABEL => $price->label, - TicketPriceDomainObjectAbstract::SALE_START_DATE => $price->sale_start_date, - TicketPriceDomainObjectAbstract::SALE_END_DATE => $price->sale_end_date, - TicketPriceDomainObjectAbstract::INITIAL_QUANTITY_AVAILABLE => $price->initial_quantity_available, - TicketPriceDomainObjectAbstract::IS_HIDDEN => $price->is_hidden, - ])); - - return $this->ticketCreateService->createTicket( - ticket: (new TicketDomainObject()) - ->setTitle($ticketsData->title) - ->setType($ticketsData->type->name) - ->setOrder($ticketsData->order) - ->setSaleStartDate($ticketsData->sale_start_date) - ->setSaleEndDate($ticketsData->sale_end_date) - ->setMaxPerOrder($ticketsData->max_per_order) - ->setDescription($ticketsData->description) - ->setMinPerOrder($ticketsData->min_per_order) - ->setIsHidden($ticketsData->is_hidden) - ->setHideBeforeSaleStartDate($ticketsData->hide_before_sale_start_date) - ->setHideAfterSaleEndDate($ticketsData->hide_after_sale_end_date) - ->setHideWhenSoldOut($ticketsData->hide_when_sold_out) - ->setStartCollapsed($ticketsData->start_collapsed) - ->setShowQuantityRemaining($ticketsData->show_quantity_remaining) - ->setIsHiddenWithoutPromoCode($ticketsData->is_hidden_without_promo_code) - ->setTicketPrices($ticketPrices) - ->setEventId($ticketsData->event_id), - accountId: $ticketsData->account_id, - taxAndFeeIds: $ticketsData->tax_and_fee_ids, - ); - } -} diff --git a/backend/app/Services/Handlers/Ticket/DTO/UpsertTicketDTO.php b/backend/app/Services/Handlers/Ticket/DTO/UpsertTicketDTO.php deleted file mode 100644 index b19e4d9798..0000000000 --- a/backend/app/Services/Handlers/Ticket/DTO/UpsertTicketDTO.php +++ /dev/null @@ -1,42 +0,0 @@ -databaseManager->transaction(function () use ($ticketId, $eventId) { - $this->deleteTicket($ticketId, $eventId); - }); - } - - /** - * @throws CannotDeleteEntityException - */ - private function deleteTicket(int $ticketId, int $eventId): void - { - $attendees = $this->attendeeRepository->findWhere( - [ - AttendeeDomainObjectAbstract::EVENT_ID => $eventId, - AttendeeDomainObjectAbstract::TICKET_ID => $ticketId, - ] - ); - - if ($attendees->count() > 0) { - throw new CannotDeleteEntityException( - __('You cannot delete this ticket because it has orders associated with it. You can hide it instead.') - ); - } - - $this->ticketRepository->deleteWhere( - [ - TicketDomainObjectAbstract::EVENT_ID => $eventId, - TicketDomainObjectAbstract::ID => $ticketId, - ] - ); - - $this->ticketPriceRepository->deleteWhere( - [ - TicketPriceDomainObjectAbstract::TICKET_ID => $ticketId, - ] - ); - - $this->logger->info(sprintf('Ticket %d was deleted from event %d', $ticketId, $eventId), [ - 'ticketId' => $ticketId, - 'eventId' => $eventId, - ]); - } -} diff --git a/backend/app/Services/Handlers/Ticket/EditTicketHandler.php b/backend/app/Services/Handlers/Ticket/EditTicketHandler.php deleted file mode 100644 index fb0e824393..0000000000 --- a/backend/app/Services/Handlers/Ticket/EditTicketHandler.php +++ /dev/null @@ -1,139 +0,0 @@ -databaseManager->transaction(function () use ($ticketsData) { - $where = [ - 'event_id' => $ticketsData->event_id, - 'id' => $ticketsData->ticket_id, - ]; - - $ticket = $this->updateTicket($ticketsData, $where); - - $this->addTaxes($ticket, $ticketsData); - - $this->priceUpdateService->updatePrices( - $ticket, - $ticketsData, - $ticket->getTicketPrices(), - $this->eventRepository->findById($ticketsData->event_id) - ); - - return $this->ticketRepository - ->loadRelation(TicketPriceDomainObject::class) - ->findById($ticket->getId()); - }); - } - - /** - * @throws CannotChangeTicketTypeException - */ - private function updateTicket(UpsertTicketDTO $ticketsData, array $where): TicketDomainObject - { - $event = $this->eventRepository->findById($ticketsData->event_id); - - $this->validateChangeInTicketType($ticketsData); - - $this->ticketRepository->updateWhere( - attributes: [ - 'title' => $ticketsData->title, - 'type' => $ticketsData->type->name, - 'order' => $ticketsData->order, - 'sale_start_date' => $ticketsData->sale_start_date - ? DateHelper::convertToUTC($ticketsData->sale_start_date, $event->getTimezone()) - : null, - 'sale_end_date' => $ticketsData->sale_end_date - ? DateHelper::convertToUTC($ticketsData->sale_end_date, $event->getTimezone()) - : null, - 'max_per_order' => $ticketsData->max_per_order, - 'description' => $this->purifier->purify($ticketsData->description), - 'min_per_order' => $ticketsData->min_per_order, - 'is_hidden' => $ticketsData->is_hidden, - 'hide_before_sale_start_date' => $ticketsData->hide_before_sale_start_date, - 'hide_after_sale_end_date' => $ticketsData->hide_after_sale_end_date, - 'hide_when_sold_out' => $ticketsData->hide_when_sold_out, - 'start_collapsed' => $ticketsData->start_collapsed, - 'show_quantity_remaining' => $ticketsData->show_quantity_remaining, - 'is_hidden_without_promo_code' => $ticketsData->is_hidden_without_promo_code, - ], - where: $where - ); - - return $this->ticketRepository - ->loadRelation(TicketPriceDomainObject::class) - ->findFirstWhere($where); - } - - /** - * @throws Exception - */ - private function addTaxes(TicketDomainObject $ticket, UpsertTicketDTO $ticketsData): void - { - $this->taxAndTicketAssociationService->addTaxesToTicket( - new TaxAndTicketAssociateParams( - ticketId: $ticket->getId(), - accountId: $ticketsData->account_id, - taxAndFeeIds: $ticketsData->tax_and_fee_ids, - ) - ); - } - - /** - * @throws CannotChangeTicketTypeException - * @todo - We should probably check reserved tickets here as well - */ - private function validateChangeInTicketType(UpsertTicketDTO $ticketsData): void - { - $ticket = $this->ticketRepository - ->loadRelation(TicketPriceDomainObject::class) - ->findById($ticketsData->ticket_id); - - $quantitySold = $ticket->getTicketPrices() - ->sum(fn(TicketPriceDomainObject $price) => $price->getQuantitySold()); - - if ($ticket->getType() !== $ticketsData->type->name && $quantitySold > 0) { - throw new CannotChangeTicketTypeException( - __('Ticket type cannot be changed as tickets have been registered for this type') - ); - } - } -} diff --git a/backend/app/Services/Handlers/Ticket/GetTicketsHandler.php b/backend/app/Services/Handlers/Ticket/GetTicketsHandler.php deleted file mode 100644 index 898e952d34..0000000000 --- a/backend/app/Services/Handlers/Ticket/GetTicketsHandler.php +++ /dev/null @@ -1,37 +0,0 @@ -ticketRepository - ->loadRelation(TicketPriceDomainObject::class) - ->loadRelation(TaxAndFeesDomainObject::class) - ->findByEventId($eventId, $queryParamsDTO); - - $filteredTickets = $this->ticketFilterService->filter( - tickets: $ticketPaginator->getCollection(), - hideSoldOutTickets: false, - ); - - $ticketPaginator->setCollection($filteredTickets); - - return $ticketPaginator; - } -} diff --git a/backend/app/Services/Handlers/Ticket/SortTicketsHandler.php b/backend/app/Services/Handlers/Ticket/SortTicketsHandler.php deleted file mode 100644 index 2506098568..0000000000 --- a/backend/app/Services/Handlers/Ticket/SortTicketsHandler.php +++ /dev/null @@ -1,41 +0,0 @@ -sortBy('order')->pluck('id')->toArray(); - - $ticketIdsResult = $this->ticketRepository->findWhere([ - 'event_id' => $eventId, - ]) - ->map(fn($ticket) => $ticket->getId()) - ->toArray(); - - // Check if the orderedTicketIds array exactly matches the ticket IDs from the database - $missingInOrdered = array_diff($ticketIdsResult, $orderedTicketIds); - $extraInOrdered = array_diff($orderedTicketIds, $ticketIdsResult); - - if (!empty($missingInOrdered) || !empty($extraInOrdered)) { - throw new ResourceConflictException( - __('The ordered ticket IDs must exactly match all tickets for the event without missing or extra IDs.') - ); - } - - $this->ticketRepository->sortTickets($eventId, $orderedTicketIds); - } -} diff --git a/backend/app/Validators/CompleteOrderValidator.php b/backend/app/Validators/CompleteOrderValidator.php index 6b8ffc5b9a..35029f92ba 100644 --- a/backend/app/Validators/CompleteOrderValidator.php +++ b/backend/app/Validators/CompleteOrderValidator.php @@ -5,23 +5,23 @@ namespace HiEvents\Validators; use HiEvents\DomainObjects\Enums\QuestionBelongsTo; +use HiEvents\DomainObjects\Generated\ProductDomainObjectAbstract; use HiEvents\DomainObjects\Generated\QuestionDomainObjectAbstract; -use HiEvents\DomainObjects\Generated\TicketDomainObjectAbstract; +use HiEvents\DomainObjects\ProductDomainObject; +use HiEvents\DomainObjects\ProductPriceDomainObject; use HiEvents\DomainObjects\QuestionDomainObject; -use HiEvents\DomainObjects\TicketDomainObject; -use HiEvents\DomainObjects\TicketPriceDomainObject; use HiEvents\Repository\Eloquent\Value\Relationship; +use HiEvents\Repository\Interfaces\ProductRepositoryInterface; use HiEvents\Repository\Interfaces\QuestionRepositoryInterface; -use HiEvents\Repository\Interfaces\TicketRepositoryInterface; -use HiEvents\Validators\Rules\AttendeeQuestionRule; use HiEvents\Validators\Rules\OrderQuestionRule; +use HiEvents\Validators\Rules\ProductQuestionRule; use Illuminate\Routing\Route; class CompleteOrderValidator extends BaseValidator { public function __construct( private readonly QuestionRepositoryInterface $questionRepository, - private readonly TicketRepositoryInterface $ticketRepository, + private readonly ProductRepositoryInterface $productRepository, private readonly Route $route ) { @@ -31,8 +31,8 @@ public function rules(): array { $questions = $this->questionRepository ->loadRelation( - new Relationship(TicketDomainObject::class, [ - new Relationship(TicketPriceDomainObject::class) + new Relationship(ProductDomainObject::class, [ + new Relationship(ProductPriceDomainObject::class) ]) ) ->findWhere( @@ -41,33 +41,22 @@ public function rules(): array $orderQuestions = $questions->filter( fn(QuestionDomainObject $question) => $question->getBelongsTo() === QuestionBelongsTo::ORDER->name ); - $ticketQuestions = $questions->filter( - fn(QuestionDomainObject $question) => $question->getBelongsTo() === QuestionBelongsTo::TICKET->name + $productQuestions = $questions->filter( + fn(QuestionDomainObject $question) => $question->getBelongsTo() === QuestionBelongsTo::PRODUCT->name ); - $tickets = $this->ticketRepository - ->loadRelation(TicketPriceDomainObject::class) + $products = $this->productRepository + ->loadRelation(ProductPriceDomainObject::class) ->findWhere( - [TicketDomainObjectAbstract::EVENT_ID => $this->route->parameter('event_id')] + [ProductDomainObjectAbstract::EVENT_ID => $this->route->parameter('event_id')] ); return [ 'order.first_name' => ['required', 'string', 'max:40'], 'order.last_name' => ['required', 'string', 'max:40'], - 'order.questions' => new OrderQuestionRule($orderQuestions, $tickets), + 'order.questions' => new OrderQuestionRule($orderQuestions, $products), 'order.email' => 'required|email', - 'attendees.*.first_name' => ['required', 'string', 'max:40'], - 'attendees.*.last_name' => ['required', 'string', 'max:40'], - 'attendees.*.email' => ['required', 'email'], - 'attendees' => new AttendeeQuestionRule($ticketQuestions, $tickets), - - // Address validation is intentionally not strict, as we want to support all countries - 'order.address.address_line_1' => ['string', 'max:255'], - 'order.address.address_line_2' => ['string', 'max:255', 'nullable'], - 'order.address.city' => ['string', 'max:85'], - 'order.address.state_or_region' => ['string', 'max:85'], - 'order.address.zip_or_postal_code' => ['string', 'max:85'], - 'order.address.country' => ['string', 'max:2'], + 'products' => new ProductQuestionRule($productQuestions, $products), ]; } @@ -77,9 +66,6 @@ public function messages(): array 'order.first_name' => __('First name is required'), 'order.last_name' => __('Last name is required'), 'order.email' => __('A valid email is required'), - 'attendees.*.first_name' => __('First name is required'), - 'attendees.*.last_name' => __('Last name is required'), - 'attendees.*.email' => __('A valid email is required'), ]; } } diff --git a/backend/app/Validators/Rules/BaseQuestionRule.php b/backend/app/Validators/Rules/BaseQuestionRule.php index 7571bf1334..f30ce6e0f1 100644 --- a/backend/app/Validators/Rules/BaseQuestionRule.php +++ b/backend/app/Validators/Rules/BaseQuestionRule.php @@ -5,8 +5,8 @@ use Closure; use HiEvents\DomainObjects\Enums\QuestionTypeEnum; use HiEvents\DomainObjects\QuestionDomainObject; -use HiEvents\DomainObjects\TicketDomainObject; -use HiEvents\DomainObjects\TicketPriceDomainObject; +use HiEvents\DomainObjects\ProductDomainObject; +use HiEvents\DomainObjects\ProductPriceDomainObject; use Illuminate\Contracts\Validation\DataAwareRule; use Illuminate\Contracts\Validation\ValidationRule; use Illuminate\Contracts\Validation\ValidatorAwareRule; @@ -34,7 +34,7 @@ abstract class BaseQuestionRule implements ValidationRule, DataAwareRule, Valida protected Collection $questions; - private Collection $tickets; + private Collection $products; protected Validator $validator; @@ -44,20 +44,20 @@ abstract protected function validateRequiredQuestionArePresent(Collection $data) abstract protected function validateQuestions(mixed $data): array; - public function __construct(Collection $questions, Collection $tickets) + public function __construct(Collection $questions, Collection $products) { $this->questions = $questions; - $this->tickets = $tickets; + $this->products = $products; } public function validate(string $attribute, mixed $value, Closure $fail): void { $this->validateRequiredQuestionArePresent(collect($value)); - $validationMessages = $this->validateQuestions($value); + $questionValidationMessages = $this->validateQuestions($value); - if ($validationMessages) { - $this->validator->messages()->merge($validationMessages); + if ($questionValidationMessages) { + $this->validator->messages()->merge($questionValidationMessages); } } @@ -73,21 +73,21 @@ public function setData(array $data): void $this->data = $data; } - protected function getTicketIdFromTicketPriceId(int $ticketPriceId): int + protected function getProductIdFromProductPriceId(int $productPriceId): int { - $ticketPrices = new Collection(); - $this->tickets->each(fn(TicketDomainObject $ticket) => $ticketPrices->push(...$ticket->getTicketPrices())); + $productPrices = new Collection(); + $this->products->each(fn(ProductDomainObject $product) => $productPrices->push(...$product->getProductPrices())); - /** @var TicketPriceDomainObject $ticketPrice */ - $ticketPrice = $ticketPrices - ->first(fn(TicketPriceDomainObject $ticketPrice) => $ticketPrice->getId() === $ticketPriceId); + /** @var ProductPriceDomainObject $productPrice */ + $productPrice = $productPrices + ->first(fn(ProductPriceDomainObject $productPrice) => $productPrice->getId() === $productPriceId); - return $ticketPrice->getTicketId(); + return $productPrice->getProductId(); } protected function isAnswerValid(QuestionDomainObject $questionDomainObject, mixed $response): bool { - if (!$questionDomainObject->isMultipleChoice()) { + if (!$questionDomainObject->isPreDefinedChoice()) { return true; } @@ -96,7 +96,7 @@ protected function isAnswerValid(QuestionDomainObject $questionDomainObject, mix } if (is_string($response['answer'])) { - return in_array($response, $questionDomainObject->getOptions(), true); + return in_array($response['answer'], $questionDomainObject->getOptions(), true); } return array_diff((array)$response['answer'], $questionDomainObject->getOptions()) === []; @@ -160,4 +160,9 @@ protected function validateResponseLength( return $validationMessages; } + + protected function getProductDomainObject(int $id): ?ProductDomainObject + { + return $this->products->filter(fn($product) => $product->getId() === $id)?->first(); + } } diff --git a/backend/app/Validators/Rules/AttendeeQuestionRule.php b/backend/app/Validators/Rules/ProductQuestionRule.php similarity index 50% rename from backend/app/Validators/Rules/AttendeeQuestionRule.php rename to backend/app/Validators/Rules/ProductQuestionRule.php index 87640c9e59..534dd92ef8 100644 --- a/backend/app/Validators/Rules/AttendeeQuestionRule.php +++ b/backend/app/Validators/Rules/ProductQuestionRule.php @@ -2,26 +2,28 @@ namespace HiEvents\Validators\Rules; +use HiEvents\DomainObjects\Enums\ProductType; use HiEvents\DomainObjects\QuestionDomainObject; use Illuminate\Support\Collection; +use Illuminate\Support\Facades\Validator; use Illuminate\Validation\ValidationException; -class AttendeeQuestionRule extends BaseQuestionRule +class ProductQuestionRule extends BaseQuestionRule { /** * @throws ValidationException */ - protected function validateRequiredQuestionArePresent(Collection $orderAttendees): void + protected function validateRequiredQuestionArePresent(Collection $orderProducts): void { - foreach ($orderAttendees as $attendee) { - $ticketId = $this->getTicketIdFromTicketPriceId($attendee['ticket_price_id']); - $questions = $attendee['questions'] ?? []; + foreach ($orderProducts as $productData) { + $productId = $this->getProductIdFromProductPriceId($productData['product_price_id']); + $questions = $productData['questions'] ?? []; $requiredQuestionIds = $this->questions - ->filter(function (QuestionDomainObject $question) use ($ticketId) { + ->filter(function (QuestionDomainObject $question) use ($productId) { return $question->getRequired() && !$question->getIsHidden() - && $question->getTickets()?->map(fn($ticket) => $ticket->getId())->contains($ticketId); + && $question->getProducts()?->map(fn($product) => $product->getId())->contains($productId); }) ->map(fn(QuestionDomainObject $question) => $question->getId()); @@ -33,15 +35,29 @@ protected function validateRequiredQuestionArePresent(Collection $orderAttendees } } - protected function validateQuestions(mixed $attendees): array + protected function validateQuestions(mixed $products): array { $validationMessages = []; - foreach ($attendees as $attendeeIndex => $attendee) { - $questions = $attendee['questions'] ?? []; + foreach ($products as $productIndex => $productRequestData) { + $productDomainObject = $this->getProductDomainObject($productRequestData['product_id']); + + if (!$productDomainObject) { + $validationMessages['products.' . $productIndex][] = __('This product is outdated. Please reload the page.'); + continue; + } + + if ($productDomainObject->getProductType() === ProductType::TICKET->name) { + $validationMessages = [ + ...$validationMessages, + ...$this->validateBasicTicketFields($productRequestData, $productIndex), + ]; + } + + $questions = $productRequestData['questions'] ?? []; foreach ($questions as $questionIndex => $question) { $questionDomainObject = $this->getQuestionDomainObject($question['question_id'] ?? null); - $key = 'attendees.' . $attendeeIndex . '.questions.' . $questionIndex . '.response'; + $key = 'products.' . $productIndex . '.questions.' . $questionIndex . '.response'; $response = empty($question['response']) ? null : $question['response']; if (!$questionDomainObject) { @@ -67,4 +83,25 @@ protected function validateQuestions(mixed $attendees): array return $validationMessages; } + + private function validateBasicTicketFields(mixed $productRequestData, int|string $productIndex): array + { + $validationMessages = []; + + $validator = Validator::make($productRequestData, [ + 'first_name' => ['required', 'string', 'min:1', 'max:100'], + 'last_name' => ['required', 'string', 'min:1', 'max:100'], + 'email' => ['required', 'string', 'email', 'max:100'], + ]); + + if ($validator->fails()) { + foreach ($validator->errors()->messages() as $field => $messages) { + foreach ($messages as $message) { + $validationMessages["products.$productIndex.$field"][] = $message; + } + } + } + + return $validationMessages; + } } diff --git a/backend/config/app.php b/backend/config/app.php index e302ce327d..86288b806f 100644 --- a/backend/config/app.php +++ b/backend/config/app.php @@ -24,12 +24,12 @@ 'homepage_views_update_batch_size' => env('APP_HOMEPAGE_VIEWS_UPDATE_BATCH_SIZE', 8), /** - * The number of seconds to cache the ticket quantities on the homepage + * The number of seconds to cache the product quantities on the homepage * It is recommended to cache this value for a short period of time for high traffic sites * * Set to null to disable caching */ - 'homepage_ticket_quantities_cache_ttl' => env('APP_HOMEPAGE_TICKET_QUANTITIES_CACHE_TTL', 2), + 'homepage_product_quantities_cache_ttl' => env('APP_HOMEPAGE_TICKET_QUANTITIES_CACHE_TTL', 2), 'frontend_urls' => [ 'confirm_email_address' => '/manage/profile/confirm-email-address/%s', @@ -39,12 +39,13 @@ 'stripe_connect_return_url' => '/account/payment', 'stripe_connect_refresh_url' => '/account/payment', 'event_homepage' => '/event/%d/%s', - 'attendee_ticket' => '/ticket/%d/%s', + 'attendee_product' => '/product/%d/%s', 'order_summary' => '/checkout/%d/%s/summary', 'organizer_order_summary' => '/manage/event/%d/orders#order-%d', ], 'email_logo_url' => env('APP_EMAIL_LOGO_URL'), + 'email_logo_link_url' => env('APP_EMAIL_LOGO_LINK_URL', env('APP_FRONTEND_URL', 'http://localhost')), 'email_footer_text' => env('APP_EMAIL_FOOTER_TEXT'), /* diff --git a/backend/database/migrations/2024_09_20_032323_rename_tickets_to_products.php b/backend/database/migrations/2024_09_20_032323_rename_tickets_to_products.php new file mode 100644 index 0000000000..907f3fabba --- /dev/null +++ b/backend/database/migrations/2024_09_20_032323_rename_tickets_to_products.php @@ -0,0 +1,182 @@ +renameColumn('ticket_id', 'product_id'); + $table->renameColumn('ticket_price_id', 'product_price_id'); + }); + + Schema::table('attendees', function (Blueprint $table) { + $table->renameColumn('ticket_id', 'product_id'); + $table->renameColumn('ticket_price_id', 'product_price_id'); + }); + + Schema::table('product_prices', function (Blueprint $table) { + $table->renameColumn('ticket_id', 'product_id'); + }); + + Schema::table('product_taxes_and_fees', function (Blueprint $table) { + $table->renameColumn('ticket_id', 'product_id'); + }); + + Schema::table('product_questions', function (Blueprint $table) { + $table->renameColumn('ticket_id', 'product_id'); + }); + + Schema::table('product_check_in_lists', function (Blueprint $table) { + $table->renameColumn('ticket_id', 'product_id'); + }); + + Schema::table('attendee_check_ins', function (Blueprint $table) { + $table->renameColumn('ticket_id', 'product_id'); + }); + + Schema::table('product_capacity_assignments', function (Blueprint $table) { + $table->renameColumn('ticket_id', 'product_id'); + }); + + Schema::table('question_answers', function (Blueprint $table) { + $table->renameColumn('ticket_id', 'product_id'); + }); + + Schema::table('promo_codes', function (Blueprint $table) { + $table->renameColumn('applicable_ticket_ids', 'applicable_product_ids'); + }); + + Schema::table('event_statistics', function (Blueprint $table) { + $table->renameColumn('tickets_sold', 'products_sold'); + }); + + Schema::table('event_daily_statistics', function (Blueprint $table) { + $table->renameColumn('tickets_sold', 'products_sold'); + }); + + Schema::table('messages', function (Blueprint $table) { + $table->renameColumn('ticket_ids', 'product_ids'); + }); + + Schema::table('event_settings', function (Blueprint $table) { + $table->renameColumn('ticket_page_message', 'product_page_message'); + }); + + $this->renameIndex('idx_ticket_prices_ticket_id', 'idx_product_prices_product_id'); + $this->renameIndex('order_items_ticket_id_index', 'order_items_product_id_index'); + $this->renameIndex('order_items_ticket_price_id_index', 'order_items_product_price_id_index'); + $this->renameIndex('idx_attendees_ticket_id_deleted_at', 'idx_attendees_product_id_deleted_at'); + $this->renameIndex('ticket_tax_and_fees_ticket_id_index', 'product_tax_and_fees_product_id_index'); + $this->renameIndex('idx_ticket_questions_active', 'idx_product_questions_active'); + $this->renameIndex('ticket_check_in_lists_ticket_id_check_in_list_id_index', 'product_check_in_lists_product_id_check_in_list_id_index'); + $this->renameIndex('idx_ticket_check_in_lists_ticket_id_deleted_at', 'idx_product_check_in_lists_product_id_deleted_at'); + $this->renameIndex('attendee_check_ins_ticket_id_index', 'attendee_check_ins_product_id_index'); + $this->renameIndex('ticket_capacity_assignments_ticket_id_index', 'product_capacity_assignments_product_id_index'); + $this->renameIndex('attendees_ticket_prices_id_fk', 'attendees_product_prices_id_fk'); + } + + public function down(): void + { + Schema::rename('products', 'tickets'); + + Schema::rename('product_prices', 'ticket_prices'); + Schema::rename('product_taxes_and_fees', 'ticket_taxes_and_fees'); + Schema::rename('product_questions', 'ticket_questions'); + Schema::rename('product_check_in_lists', 'ticket_check_in_lists'); + Schema::rename('product_capacity_assignments', 'ticket_capacity_assignments'); + + // Rename sequences back + DB::statement('ALTER SEQUENCE product_capacity_assignments_id_seq RENAME TO ticket_capacity_assignments_id_seq'); + DB::statement('ALTER SEQUENCE product_check_in_lists_id_seq RENAME TO ticket_check_in_lists_id_seq'); + + Schema::table('order_items', function (Blueprint $table) { + $table->renameColumn('product_id', 'ticket_id'); + $table->renameColumn('product_price_id', 'ticket_price_id'); + }); + + Schema::table('attendees', function (Blueprint $table) { + $table->renameColumn('product_id', 'ticket_id'); + $table->renameColumn('product_price_id', 'ticket_price_id'); + }); + + Schema::table('ticket_prices', function (Blueprint $table) { + $table->renameColumn('product_id', 'ticket_id'); + }); + + Schema::table('ticket_taxes_and_fees', function (Blueprint $table) { + $table->renameColumn('product_id', 'ticket_id'); + }); + + Schema::table('ticket_questions', function (Blueprint $table) { + $table->renameColumn('product_id', 'ticket_id'); + }); + + Schema::table('ticket_check_in_lists', function (Blueprint $table) { + $table->renameColumn('product_id', 'ticket_id'); + }); + + Schema::table('attendee_check_ins', function (Blueprint $table) { + $table->renameColumn('product_id', 'ticket_id'); + }); + + Schema::table('ticket_capacity_assignments', function (Blueprint $table) { + $table->renameColumn('product_id', 'ticket_id'); + }); + + Schema::table('question_answers', function (Blueprint $table) { + $table->renameColumn('product_id', 'ticket_id'); + }); + + Schema::table('promo_codes', function (Blueprint $table) { + $table->renameColumn('applicable_product_ids', 'applicable_ticket_ids'); + }); + + Schema::table('event_statistics', function (Blueprint $table) { + $table->renameColumn('products_sold', 'tickets_sold'); + }); + + Schema::table('event_daily_statistics', function (Blueprint $table) { + $table->renameColumn('products_sold', 'tickets_sold'); + }); + + Schema::table('messages', function (Blueprint $table) { + $table->renameColumn('product_ids', 'ticket_ids'); + }); + + Schema::table('event_settings', function (Blueprint $table) { + $table->renameColumn('product_page_message', 'ticket_page_message'); + }); + + $this->renameIndex('idx_product_prices_product_id', 'idx_ticket_prices_ticket_id'); + $this->renameIndex('order_items_product_id_index', 'order_items_ticket_id_index'); + $this->renameIndex('order_items_product_price_id_index', 'order_items_ticket_price_id_index'); + $this->renameIndex('idx_attendees_product_id_deleted_at', 'idx_attendees_ticket_id_deleted_at'); + $this->renameIndex('product_tax_and_fees_product_id_index', 'ticket_tax_and_fees_ticket_id_index'); + $this->renameIndex('idx_product_questions_active', 'idx_ticket_questions_active'); + $this->renameIndex('product_check_in_lists_product_id_check_in_list_id_index', 'ticket_check_in_lists_ticket_id_check_in_list_id_index'); + $this->renameIndex('idx_product_check_in_lists_product_id_deleted_at', 'idx_ticket_check_in_lists_ticket_id_deleted_at'); + $this->renameIndex('attendee_check_ins_product_id_index', 'attendee_check_ins_ticket_id_index'); + $this->renameIndex('product_capacity_assignments_product_id_index', 'ticket_capacity_assignments_ticket_id_index'); + $this->renameIndex('attendees_product_prices_id_fk', 'attendees_ticket_prices_id_fk'); + } + + private function renameIndex($from, $to): void + { + DB::statement("ALTER INDEX IF EXISTS {$from} RENAME TO {$to}"); + } +}; diff --git a/backend/database/migrations/2024_09_20_032838_add_product_type_to_products.php b/backend/database/migrations/2024_09_20_032838_add_product_type_to_products.php new file mode 100644 index 0000000000..fd8974b75a --- /dev/null +++ b/backend/database/migrations/2024_09_20_032838_add_product_type_to_products.php @@ -0,0 +1,24 @@ +enum('product_type', ProductType::valuesArray()) + ->default(ProductType::TICKET->name) + ->after('id'); + }); + } + + public function down(): void + { + Schema::table('products', static function (Blueprint $table) { + $table->dropColumn('product_type'); + }); + } +}; diff --git a/backend/database/migrations/2024_09_23_032009_add_product_categories_table.php b/backend/database/migrations/2024_09_23_032009_add_product_categories_table.php new file mode 100644 index 0000000000..5a4b2f8824 --- /dev/null +++ b/backend/database/migrations/2024_09_23_032009_add_product_categories_table.php @@ -0,0 +1,68 @@ +id(); + $table->string('name'); + $table->string('no_products_message')->nullable(); + $table->string('description')->nullable(); + $table->boolean('is_hidden')->default(false); + $table->tinyInteger('order')->default(0); + + $table->unsignedBigInteger('event_id'); + $table->foreign('event_id')->references('id')->on('events')->onDelete('cascade'); + + $table->timestamps(); + $table->softDeletes(); + + $table->index('event_id'); + $table->index('is_hidden'); + $table->index('order'); + }); + + Schema::table('products', static function (Blueprint $table) { + $table->unsignedBigInteger('product_category_id')->nullable(); + $table->foreign('product_category_id')->references('id')->on('product_categories')->onDelete('set null'); + }); + + $events = DB::table('events')->get(); + + foreach ($events as $event) { + $categoryId = DB::table('product_categories')->insertGetId([ + 'name' => __('Tickets'), + 'event_id' => $event->id, + 'created_at' => now(), + 'updated_at' => now(), + ]); + + DB::table('products') + ->where('event_id', $event->id) + ->update(['product_category_id' => $categoryId]); + } + + DB::table('questions') + ->where('belongs_to', 'TICKET') + ->update(['belongs_to' => 'PRODUCT']); + } + + public function down(): void + { + Schema::table('products', static function (Blueprint $table) { + $table->dropForeign(['product_category_id']); + $table->dropColumn('product_category_id'); + }); + + Schema::dropIfExists('product_categories'); + + DB::table('questions') + ->where('belongs_to', 'PRODUCT') + ->update(['belongs_to' => 'TICKET']); + } +}; diff --git a/backend/database/migrations/2024_09_29_053757_add_product_type_to_order_items_table.php b/backend/database/migrations/2024_09_29_053757_add_product_type_to_order_items_table.php new file mode 100644 index 0000000000..79f7a26e7f --- /dev/null +++ b/backend/database/migrations/2024_09_29_053757_add_product_type_to_order_items_table.php @@ -0,0 +1,22 @@ +string('product_type')->default(ProductType::TICKET->name); + }); + } + + public function down(): void + { + Schema::table('order_items', function (Blueprint $table) { + $table->dropColumn('product_type'); + }); + } +}; diff --git a/backend/database/migrations/2024_10_01_003655_update_question_and_answer_views_view.php b/backend/database/migrations/2024_10_01_003655_update_question_and_answer_views_view.php new file mode 100644 index 0000000000..4f0db80e33 --- /dev/null +++ b/backend/database/migrations/2024_10_01_003655_update_question_and_answer_views_view.php @@ -0,0 +1,54 @@ +unsignedInteger('attendees_registered')->default(0); + }); + Schema::table('event_daily_statistics', static function (Blueprint $table) { + $table->unsignedInteger('attendees_registered')->default(0); + }); + } + + public function down(): void + { + Schema::table('event_statistics_tables', static function (Blueprint $table) { + $table->dropColumn('attendees_registered'); + }); + + Schema::table('event_daily_statistics', static function (Blueprint $table) { + $table->dropColumn('attendees_registered'); + }); + } +}; diff --git a/backend/database/migrations/2024_10_14_232118_add_start_collapsed_to_tickets.php b/backend/database/migrations/2024_10_14_232118_add_start_collapsed_to_tickets.php index a453959c92..480a9fe31b 100644 --- a/backend/database/migrations/2024_10_14_232118_add_start_collapsed_to_tickets.php +++ b/backend/database/migrations/2024_10_14_232118_add_start_collapsed_to_tickets.php @@ -2,21 +2,25 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; -use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; -return new class extends Migration -{ +return new class extends Migration { public function up(): void { - Schema::table('tickets', static function (Blueprint $table) { - $table->boolean('start_collapsed')->default(false); - }); + $table = Schema::hasTable('tickets') ? 'tickets' : 'products'; + + if (!Schema::hasColumn($table, 'start_collapsed')) { + Schema::table($table, static function (Blueprint $table) { + $table->boolean('start_collapsed')->default(false); + }); + } } public function down(): void { - Schema::table('tickets', static function (Blueprint $table) { + $table = Schema::hasTable('tickets') ? 'tickets' : 'products'; + + Schema::table($table, static function (Blueprint $table) { $table->dropColumn('start_collapsed'); }); } diff --git a/backend/database/migrations/schema.sql b/backend/database/migrations/schema.sql index bb359414a7..4b20e570d5 100644 --- a/backend/database/migrations/schema.sql +++ b/backend/database/migrations/schema.sql @@ -276,7 +276,6 @@ create table if not exists tickets deleted_at timestamp, type varchar(20) default 'PAID'::character varying not null, is_hidden boolean default false, - start_collapsed boolean default false, primary key (id), constraint fk_tickets_event_id foreign key (event_id) references events diff --git a/backend/resources/views/emails/orders/attendee-ticket.blade.php b/backend/resources/views/emails/orders/attendee-ticket.blade.php index 9379fb26f1..dcad7287a3 100644 --- a/backend/resources/views/emails/orders/attendee-ticket.blade.php +++ b/backend/resources/views/emails/orders/attendee-ticket.blade.php @@ -21,9 +21,8 @@ {{ __('If you have any questions or need assistance, please reply to this email or contact the event organizer') }} {{ __('at') }} {{$eventSettings->getSupportEmail()}}. -{{ __('Best regards,') }} -
-{{config('app.name')}} +{{ __('Best regards,') }}
+{{ config('app.name') }}