diff --git a/.gitmessage.txt b/.gitmessage.txt index 733443cce..c02d378a4 100644 --- a/.gitmessage.txt +++ b/.gitmessage.txt @@ -23,8 +23,4 @@ # BREAKING CHANGE: # Closes #123 # Relates-to #456 -<<<<<<< HEAD # Co-authored-by: Name -======= -# Co-authored-by: Name ->>>>>>> cab387aa (fix: Attendee Ticket Serializer) diff --git a/app/Http/Controllers/Apis/Protected/Summit/OAuth2SummitTicketApiController.php b/app/Http/Controllers/Apis/Protected/Summit/OAuth2SummitTicketApiController.php index 762ff8920..c43e6a968 100644 --- a/app/Http/Controllers/Apis/Protected/Summit/OAuth2SummitTicketApiController.php +++ b/app/Http/Controllers/Apis/Protected/Summit/OAuth2SummitTicketApiController.php @@ -26,6 +26,7 @@ use models\exceptions\ValidationException; use models\oauth2\IResourceServerContext; use models\summit\IOrderConstants; +use models\summit\ISummitAttendeeRepository; use models\summit\ISummitAttendeeTicketRepository; use models\summit\ISummitRepository; use models\summit\Summit; @@ -71,9 +72,12 @@ public function getChildSerializer() */ private $service; + private $attendee_repository; + /** * OAuth2SummitTicketApiController constructor. * @param ISummitRepository $summit_repository + * @param ISummitAttendeeRepository $attendee_repository, * @param ISummitAttendeeTicketRepository $repository * @param ISummitOrderService $service * @param IResourceServerContext $resource_server_context @@ -81,6 +85,7 @@ public function getChildSerializer() public function __construct ( ISummitRepository $summit_repository, + ISummitAttendeeRepository $attendee_repository, ISummitAttendeeTicketRepository $repository, ISummitOrderService $service, IResourceServerContext $resource_server_context @@ -89,6 +94,7 @@ public function __construct parent::__construct($resource_server_context); $this->repository = $repository; $this->summit_repository = $summit_repository; + $this->attendee_repository = $attendee_repository; $this->service = $service; } @@ -274,7 +280,7 @@ public function getAllBySummitCSV($summit_id) { $summit = SummitFinderStrategyFactory::build($this->summit_repository, $this->getResourceServerContext())->find($summit_id); if (is_null($summit)) return $this->error404(); - + $questions = $summit->getOrderExtraQuestionsByUsage(SummitOrderExtraQuestionTypeConstants::TicketQuestionUsage); return $this->_getAllCSV( function () { return [ @@ -446,8 +452,25 @@ function () use ($summit) { sprintf('tickets-%s-', $summit_id), [ 'features_types' => $summit->getBadgeFeaturesTypes(), - 'ticket_questions' => $summit->getOrderExtraQuestionsByUsage(SummitOrderExtraQuestionTypeConstants::TicketQuestionUsage) - ] + 'ticket_questions' => $questions + ], + null, + function($data, $serializerParams) use($questions){ + + $owners = []; + foreach ($data->getItems() as $t){ + if ($t->hasOwner()) $owners[] = $t->getOwner()->getId(); + } + $questionIds = []; + foreach ($questions as $q) { + $questionIds[] = $q->getId(); + } + $questionIds = array_values(array_unique($questionIds)); + $owners = array_values(array_unique($owners)); + + $serializerParams['answers_by_owner'] = $this->attendee_repository->getExtraQuestionAnswersByOwners($owners, $questionIds); + return $serializerParams; + } ); } @@ -932,4 +955,4 @@ public function canPrintAttendeeBadge($summit_id, $ticket_id, $view_type) ); }); } -} \ No newline at end of file +} diff --git a/app/Http/Controllers/Apis/Protected/Summit/Traits/ParametrizedGetAll.php b/app/Http/Controllers/Apis/Protected/Summit/Traits/ParametrizedGetAll.php index a75c16a53..e9f6eb3f8 100644 --- a/app/Http/Controllers/Apis/Protected/Summit/Traits/ParametrizedGetAll.php +++ b/app/Http/Controllers/Apis/Protected/Summit/Traits/ParametrizedGetAll.php @@ -178,7 +178,8 @@ public function _getAllCSV callable $getColumns, string $file_prefix = 'file-', array $serializerParams = [], - callable $queryCallable = null + callable $queryCallable = null, + callable $preProcessSerializerParams = null, ) { @@ -192,7 +193,8 @@ public function _getAllCSV $getColumns, $file_prefix, $serializerParams, - $queryCallable + $queryCallable, + $preProcessSerializerParams ) { $values = Request::all(); @@ -247,6 +249,9 @@ public function _getAllCSV $serializerParams['filter'] = $filter; + if(!is_null($preProcessSerializerParams)) + $serializerParams = call_user_func($preProcessSerializerParams, $data, $serializerParams); + $list = $data->toArray ( SerializerUtils::getExpand(), diff --git a/app/Jobs/Emails/AbstractSummitEmailJob.php b/app/Jobs/Emails/AbstractSummitEmailJob.php index 8f01f1d1d..efc2a7ada 100644 --- a/app/Jobs/Emails/AbstractSummitEmailJob.php +++ b/app/Jobs/Emails/AbstractSummitEmailJob.php @@ -12,6 +12,7 @@ * limitations under the License. **/ +use App\Services\Apis\IMailApi; use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\Config; use Illuminate\Support\Facades\Log; @@ -89,14 +90,22 @@ public function __construct })->toArray(); $payload[IMailTemplatesConstants::main_venue_address] = implode(' - ', $main_venue_addresses); - - $payload = array_merge($payload, self::getMarketingVariables($summit)); parent::__construct($payload, $template_identifier, $to_email, $subject, $cc_email, $bcc_email); } - private static function getMarketingVariables(Summit $summit) { + public function handle + ( + IMailApi $api + ){ + $summit_id = $this->payload[IMailTemplatesConstants::summit_id]; + Log::debug(sprintf("AbstractSummitEmailJob::handle summit %s", $summit_id)); + $this->payload = array_merge($this->payload, self::getMarketingVariables($summit_id)); + parent::handle($api); + } + + private static function getMarketingVariables(int $summit_id) { - Log::debug(sprintf("AbstractSummitEmailJob::getMarketingVariables summit %s", $summit->getId())); + Log::debug(sprintf("AbstractSummitEmailJob::getMarketingVariables summit %s", $summit_id)); $default_email_template_vars = collect(Config::get('marketing.default_email_template_vars')) ->mapWithKeys(function ($value, $key) { @@ -117,7 +126,7 @@ private static function getMarketingVariables(Summit $summit) { } try { - $marketing_vars = $marketing_api->getConfigValues($summit->getId(), 'EMAIL_TEMPLATE_'); + $marketing_vars = $marketing_api->getConfigValues($summit_id, 'EMAIL_TEMPLATE_'); } catch(\Exception $ex){ Log::error($ex); Log::warning @@ -138,7 +147,7 @@ private static function getMarketingVariables(Summit $summit) { sprintf ( "AbstractSummitEmailJob::getMarketingVariables summit %s marketing var %s not found or empty, using default value (%s).", - $summit->getId(), + $summit_id, $key, $value ) @@ -153,7 +162,7 @@ private static function getMarketingVariables(Summit $summit) { sprintf ( "AbstractSummitEmailJob::getMarketingVariables summit %s injecting marketing_vars %s", - $summit->getId(), + $summit_id, json_encode($marketing_vars) ) ); @@ -185,4 +194,4 @@ public static function getEmailTemplateSchema(): array{ return $payload; } -} \ No newline at end of file +} diff --git a/app/ModelSerializers/Summit/Registration/SummitAttendeeSerializer.php b/app/ModelSerializers/Summit/Registration/SummitAttendeeSerializer.php index 0d73bd613..ec1acf87c 100644 --- a/app/ModelSerializers/Summit/Registration/SummitAttendeeSerializer.php +++ b/app/ModelSerializers/Summit/Registration/SummitAttendeeSerializer.php @@ -84,7 +84,6 @@ public function serialize($expand = null, array $fields = [], array $relations = $serializer_type = $params['serializer_type']; $summit = $attendee->getSummit(); - $attendee->updateStatus(); $beginVotingDate = $params['begin_attendee_voting_period_date'] ?? null; $endVotingDate = $params['end_attendee_voting_period_date'] ?? null; $track_group_id = $params['presentation_votes_track_group_id'] ?? null; @@ -347,4 +346,4 @@ public function serialize($expand = null, array $fields = [], array $relations = }); } -} \ No newline at end of file +} diff --git a/app/ModelSerializers/Summit/Registration/SummitAttendeeTicketCSVSerializer.php b/app/ModelSerializers/Summit/Registration/SummitAttendeeTicketCSVSerializer.php index 16baadf15..90a2fa64b 100644 --- a/app/ModelSerializers/Summit/Registration/SummitAttendeeTicketCSVSerializer.php +++ b/app/ModelSerializers/Summit/Registration/SummitAttendeeTicketCSVSerializer.php @@ -80,6 +80,7 @@ public function serialize($expand = null, array $fields = [], array $relations = $ticket_owner = $ticket->getOwner(); if (isset($params['ticket_questions'])) { + $answersByOwner = $params['answers_by_owner'] ?? null; foreach ($params['ticket_questions'] as $question) { if (!$question instanceof SummitOrderExtraQuestionType) continue; @@ -88,7 +89,7 @@ public function serialize($expand = null, array $fields = [], array $relations = $values[$question_label] = ''; if (!is_null($ticket_owner)) { - $value = $ticket_owner->getExtraQuestionAnswerValueByQuestion($question); + $value = $answersByOwner[$ticket_owner->getId()][$question->getId()] ?? null; if(is_null($value)) continue; $cacheKey = $question->getId() . '|' . $value; @@ -140,4 +141,4 @@ public function serialize($expand = null, array $fields = [], array $relations = return $values; } -} \ No newline at end of file +} diff --git a/app/Models/Foundation/Summit/Registration/Attendees/SummitAttendee.php b/app/Models/Foundation/Summit/Registration/Attendees/SummitAttendee.php index 11ecf0329..763afd1a3 100644 --- a/app/Models/Foundation/Summit/Registration/Attendees/SummitAttendee.php +++ b/app/Models/Foundation/Summit/Registration/Attendees/SummitAttendee.php @@ -749,7 +749,6 @@ private function warmExtraQuestionAnswersCache():void{ $map = []; foreach ($rows as $row) { - // QuestionID es int; Value es string (para multiselect puede ser CSV/JSON segĂșn tu modelo) $map[(int)$row['QuestionID']] = $row['Value']; } $this->extraQuestionAnswersCache = $map; diff --git a/app/Models/Foundation/Summit/Repositories/ISummitAttendeeRepository.php b/app/Models/Foundation/Summit/Repositories/ISummitAttendeeRepository.php index ef7bff66c..e3d9237ca 100644 --- a/app/Models/Foundation/Summit/Repositories/ISummitAttendeeRepository.php +++ b/app/Models/Foundation/Summit/Repositories/ISummitAttendeeRepository.php @@ -96,4 +96,11 @@ public function deleteAllBySummit(int $summit_id):bool; */ public function getBySummitAndFirstNameAndLastNameAndManager(Summit $summit, string $first_name, string $last_name, SummitAttendee $manager):?SummitAttendee; + + /** + * @param array $owners + * @param array $questions + * @return array + */ + public function getExtraQuestionAnswersByOwners(array $owners, array $questions):array; } \ No newline at end of file diff --git a/app/Repositories/DoctrineRepository.php b/app/Repositories/DoctrineRepository.php index 7f4b9764e..62542e841 100644 --- a/app/Repositories/DoctrineRepository.php +++ b/app/Repositories/DoctrineRepository.php @@ -17,6 +17,7 @@ use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; use Doctrine\ORM\LazyCriteriaCollection; +use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\NativeQuery; use Doctrine\ORM\Query; use Doctrine\ORM\Query\ResultSetMappingBuilder; @@ -39,6 +40,12 @@ */ abstract class DoctrineRepository extends EntityRepository implements IBaseRepository { + protected $fetchJoinCollection = true; + + public function __construct(EntityManagerInterface $em, ClassMetadata $class) + { + parent::__construct($em, $class); + } /** * @var string @@ -189,7 +196,7 @@ protected function getParametrizedAllByPage Log::debug(sprintf("DoctrineRepository::getParametrizedAllByPage DQL %s", $query->getDQL())); $start = time(); - $paginator = new Paginator($query, $fetchJoinCollection = true); + $paginator = new Paginator($query, $this->fetchJoinCollection); $total = $paginator->count(); $end = time(); $delta = $end - $start; @@ -283,7 +290,7 @@ public function getAllByPage(PagingInfo $paging_info, Filter $filter = null, Ord ->setFirstResult($paging_info->getOffset()) ->setMaxResults($paging_info->getPerPage()); - $paginator = new Paginator($query, $fetchJoinCollection = true); + $paginator = new Paginator($query, $this->fetchJoinCollection); $total = $paginator->count(); $data = []; @@ -300,6 +307,35 @@ public function getAllByPage(PagingInfo $paging_info, Filter $filter = null, Ord ); } + /** + * @param Filter|null $filter + * @param Order|null $order + * @return int + */ + public function getFastCount(Filter $filter = null, Order $order = null){ + $query = $this->getEntityManager() + ->createQueryBuilder() + ->select('COUNT(DISTINCT e.id)') + ->from($this->getBaseEntity(), "e") + ->distinct(false); + + $query = $this->applyExtraJoins($query, $filter, $order); + + $query = $this->applyExtraSelects($query, $filter, $order); + + if(!is_null($filter)){ + $filter->apply2Query($query, $this->getFilterMappings($filter)); + } + + $query = $this->applyExtraFilters($query); + + if(!is_null($order)){ + $order->apply2Query($query, $this->getOrderMappings($filter)); + } + + return (int) $query->getQuery()->getSingleScalarResult(); + } + /** * @param PagingInfo $paging_info * @param Filter|null $filter @@ -518,7 +554,7 @@ protected function getAllAbstractByPage ->setFirstResult($paging_info->getOffset()) ->setMaxResults($paging_info->getPerPage()); - $paginator = new Paginator($query, $fetchJoinCollection = true); + $paginator = new Paginator($query, $this->fetchJoinCollection); $total = $paginator->count(); $data = array(); @@ -534,4 +570,4 @@ protected function getAllAbstractByPage $data ); } -} \ No newline at end of file +} diff --git a/app/Repositories/Summit/DoctrineSummitAttendeeRepository.php b/app/Repositories/Summit/DoctrineSummitAttendeeRepository.php index 25ae3a2a9..dc7dfe05d 100644 --- a/app/Repositories/Summit/DoctrineSummitAttendeeRepository.php +++ b/app/Repositories/Summit/DoctrineSummitAttendeeRepository.php @@ -285,7 +285,7 @@ protected function getOrderMappings() 'member_id' => 'm.id', 'status' => 'e.status', 'email' => << 'COUNT(pv.id)', 'summit_hall_checked_in_date' => 'e.summit_hall_checked_in_date', @@ -556,4 +556,41 @@ public function getBySummitAndFirstNameAndLastNameAndManager(Summit $summit, str return $query->getQuery()->getOneOrNullResult(); } -} \ No newline at end of file + /** + * @param array $owners + * @param array $questions + * @return array + * @throws \Doctrine\DBAL\Exception + */ + public function getExtraQuestionAnswersByOwners(array $owners, array $questions): array + { + $sql = <<getEntityManager()->getConnection()->executeQuery + ( + $sql, + [ + 'owner_ids' => $owners, + 'question_ids' => $questions + ], + [ + 'owner_ids' => \Doctrine\DBAL\ArrayParameterType::INTEGER, + 'question_ids' => \Doctrine\DBAL\ArrayParameterType::INTEGER + ] + ); + + $answersByOwner = []; + foreach ($stmt->fetchAllAssociative() as $r) { + $answersByOwner[(int)$r['owner_id']][(int)$r['question_id']] = (string)$r['value']; + } + + return $answersByOwner; + + } +} diff --git a/app/Repositories/Summit/DoctrineSummitAttendeeTicketRepository.php b/app/Repositories/Summit/DoctrineSummitAttendeeTicketRepository.php index 22c23b503..591772cdb 100644 --- a/app/Repositories/Summit/DoctrineSummitAttendeeTicketRepository.php +++ b/app/Repositories/Summit/DoctrineSummitAttendeeTicketRepository.php @@ -16,7 +16,10 @@ use App\Http\Utils\Filters\DoctrineNotInFilterMapping; use App\Repositories\SilverStripeDoctrineRepository; use Doctrine\DBAL\LockMode; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\QueryBuilder; +use Illuminate\Support\Facades\Log; use models\summit\IOrderConstants; use models\summit\ISummitAttendeeTicketRepository; use models\summit\ISummitRefundRequestConstants; @@ -30,6 +33,7 @@ use utils\Filter; use utils\Order; use utils\PagingInfo; +use utils\PagingResponse; /** * Class DoctrineSummitAttendeeTicketRepository @@ -39,6 +43,147 @@ final class DoctrineSummitAttendeeTicketRepository extends SilverStripeDoctrineRepository implements ISummitAttendeeTicketRepository { + public function __construct(EntityManagerInterface $em, ClassMetadata $class) + { + parent::__construct($em, $class); + $this->fetchJoinCollection = false; + } + + + /** @var array}> */ + private array $joinCatalog = [ + 'o' => ['e.order', 'join', []], + 's' => ['o.summit', 'join', ['o']], + 'ord_m' => ['o.owner', 'leftJoin', ['o']], + 'a' => ['e.owner', 'leftJoin', []], + 'a_c' => ['a.company', 'leftJoin', ['a']], + 'm' => ['a.member', 'leftJoin', ['a']], + 'am' => ['a.manager', 'leftJoin', ['a']], + 'm2' => ['am.member', 'leftJoin', ['am']], + 'b' => ['e.badge', 'leftJoin', []], + 'bt' => ['b.type', 'leftJoin', ['b']], + 'al' => ['bt.access_levels', 'leftJoin', ['bt']], + 'bf' => ['b.features', 'leftJoin', ['b']], + 'bt_bf' => ['bt.badge_features', 'leftJoin', ['bt']], + 'prt' => ['b.prints', 'leftJoin', ['b']], + 'rr' => ['e.refund_requests', 'leftJoin', []], + 'ta' => ['e.applied_taxes', 'leftJoin', []], + 'tt' => ['e.ticket_type', 'join', []], + 'pc' => ['e.promo_code', 'leftJoin', []], + 'pct' => ['pc.tags', 'leftJoin', ['pc']], + 'avt' => ['bt.allowed_view_types', 'join', ['bt']], + ]; + + private function ensureJoin(QueryBuilder $qb, string $alias): void + { + if (\in_array($alias, $qb->getAllAliases(), true)) return; + + [$path, $type, $deps] = $this->joinCatalog[$alias] ?? [null, null, []]; + foreach ($deps as $dep) $this->ensureJoin($qb, $dep); + + if ($type === 'join') $qb->join($path, $alias); + else $qb->leftJoin($path, $alias); + } + + /** + * choose alias needed + * @return string[] + */ + private function requiredAliases(?Filter $filter, ?Order $order): array + { + $need = []; // owner always + + $has = fn(string $f) => $filter?->hasFilter($f) ?? false; + $ord = fn(string $f) => $order?->hasOrder($f) ?? false; + $val = fn(string $f) => $filter?->getValue($f)[0] ?? null; + + // --- Filters --- + if ($has('order_number') || $has('order_id') || $has('order_owner_id') || $has('bought_date') || $has('summit_id')) { + $need['o'] = true; + if($has('order_owner_id')){ + $this->joinCatalog['ord_m'][1] = 'join'; + $need['ord_m'] = true; + } + } + if ($has('summit_id')) $need['s'] = true; + + if ($has('owner_first_name') || $has('owner_last_name') || $has('owner_name') || $has('owner_id') || $has('member_id')) { + $need['a'] = true; + if($has('owner_first_name') || $has('owner_last_name') || $has('owner_name') || $has('member_id')) $need['m'] = true; + } + + if ($has('owner_email')) { + $need['a'] = $need['m'] = $need['am'] = $need['m2'] = true; + } + + if ($has('owner_company') || $has('has_owner_company')) { $need['a'] = $need['a_c'] = true; } + + if ($has('has_owner')) { + if ((string)$val('has_owner') === '1') $this->joinCatalog['a'][1] = 'join'; + $need['a'] = true; + } + + if ($has('owner_status')) { + $this->joinCatalog['a'][1] = 'join'; + $need['a'] = true; + } + + if ($has('has_order_owner')) { + if ((string)$val('has_order_owner') === '1') $this->joinCatalog['ord_m'][1] = 'join'; + $need['o'] = $need['ord_m'] = true; + } + + if ($has('assigned_to')) { $need['a'] = true; $need['m'] = true; } // usa m.id y a.email + + if ($has('promo_code') || $has('promo_code_id') || $has('promo_code_description')) { + $need['pc'] = true; + } + if ($has('promo_code_tag') || $has('promo_code_tag_id')) { + $need['pc'] = $need['pct'] = true; + } + + if ($has('ticket_type_id') || $ord('ticket_type')) $need['tt'] = true; + + if ($has('has_badge') || $ord('badge_type') || $ord('badge_type_id') || $has('badge_type_id')) { + $need['b'] = $need['bt'] = true; + } + + if ($has('access_level_type_id') || $has('access_level_type_name') || $has('is_printable')) { + $need['b'] = $need['bt'] = $need['al'] = $need['a'] = true; + if ($has('is_printable') && (string)$val('is_printable') === '1') { + $this->joinCatalog['a'][1] = 'join'; + $this->joinCatalog['bt'][1] = 'join'; + $this->joinCatalog['al'][1] = 'join'; + } + } + + if ($has('badge_features_id')) { + $need['b'] = $need['bt'] = $need['bf'] = $need['bt_bf'] = true; + } + + if ($has('has_badge_prints') || $ord('badge_prints_count')) { + $need['b'] = $need['prt'] = true; + } + + if ($has('has_requested_refund_requests') || $ord('refunded_amount') || $ord('final_amount_adjusted')) { + $need['rr'] = true; + } + + if ($has('view_type_id')) { + $need['b'] = $need['bt'] = $need['avt'] = true; + } + + // --- Orders --- + if ($ord('owner_first_name') || $ord('owner_last_name') || $ord('owner_name')) { + $need['a'] = $need['m'] = true; + } + if ($ord('owner_company')) { $need['a'] = $need['a_c'] = true; } + if ($ord('owner_email')) { $need['a'] = $need['m'] = true; } + if ($ord('promo_code')) { $need['pc'] = true; } + + return array_keys($need); + } + /** * @return string @@ -55,24 +200,34 @@ protected function getBaseEntity() * @return QueryBuilder */ protected function applyExtraSelects(QueryBuilder $query, ?Filter $filter = null, ?Order $order = null):QueryBuilder{ - //$query = $query->addSelect("COALESCE(SUM(ta.amount),0) AS HIDDEN HIDDEN_APPLIED_TAXES"); - - if(!is_null($order)){ - if ($order->hasOrder('final_amount')) - $query = $query->addSelect("(e.raw_cost - e.discount) AS HIDDEN HIDDEN_FINAL_AMOUNT"); - if ($order->hasOrder('refunded_amount')) - $query = $query->addSelect("COALESCE(SUM(rr.refunded_amount),0) AS HIDDEN HIDDEN_REFUNDED_AMOUNT"); + $needsAggregation = false; - if ($order->hasOrder('final_amount_adjusted')) - $query = $query->addSelect("( (e.raw_cost - e.discount) - COALESCE(SUM(rr.refunded_amount),0) ) AS HIDDEN HIDDEN_FINAL_AMOUNT_ADJUSTED"); + if ($order) { + if ($order->hasOrder('final_amount')) { + $query->addSelect("(e.raw_cost - e.discount) AS HIDDEN HIDDEN_FINAL_AMOUNT"); + } + if ($order->hasOrder('refunded_amount')) { + $query->addSelect("COALESCE(SUM(rr.refunded_amount),0) AS HIDDEN HIDDEN_REFUNDED_AMOUNT"); + $needsAggregation = true; + } + if ($order->hasOrder('final_amount_adjusted')) { + $query->addSelect("((e.raw_cost - e.discount) - COALESCE(SUM(rr.refunded_amount),0)) AS HIDDEN HIDDEN_FINAL_AMOUNT_ADJUSTED"); + $needsAggregation = true; + } + if ($order->hasOrder('badge_prints_count')) { + $query->addSelect("COUNT(prt.id) AS HIDDEN HIDDEN_BADGE_PRINTS_COUNT"); + $needsAggregation = true; + } + } - if ($order->hasOrder('badge_prints_count')) - $query = $query->addSelect("COUNT(prt.id) AS HIDDEN HIDDEN_BADGE_PRINTS_COUNT"); + if ($needsAggregation) { + $query->groupBy('e.id'); } - $query->groupBy("e"); + return $query; } + /** * @return array */ @@ -82,6 +237,7 @@ protected function getFilterMappings() $filter = count($args) > 0 ? $args[0] : null; $owner_member_id = 0; $owner_member_email = null; + if($filter instanceof Filter) { if ($filter->hasFilter("owner_member_id")) { $owner_member_id = $filter->getValue("owner_member_id")[0]; @@ -276,37 +432,16 @@ protected function getFilterMappings() */ protected function applyExtraJoins(QueryBuilder $query, ?Filter $filter = null, ?Order $order = null) { - $query->join("e.order", "o"); - $query = $query->join("o.summit", "s"); - $query = $query->leftJoin("o.owner", "ord_m"); - $query = $query->leftJoin("e.owner", "a"); - $query = $query->leftJoin("a.company", "a_c"); - $query = $query->leftJoin("e.badge", "b"); - $query = $query->leftJoin("b.features", "bf"); - $query = $query->leftJoin("b.prints", "prt"); - $query = $query->leftJoin("b.type", "bt"); - $query = $query->leftJoin("bt.access_levels", "al"); - $query = $query->leftJoin('bt.badge_features','bt_bf'); - $query = $query->leftJoin("a.member", "m"); - $query = $query->leftJoin("e.refund_requests", "rr"); - $query = $query->leftJoin("e.applied_taxes", "ta"); - $query = $query->join("e.ticket_type", "tt"); - $query = $query->leftJoin("e.promo_code", "pc"); - - if ($filter->hasFilter('promo_code_tag_id') || $filter->hasFilter('promo_code_tag')) { - if (!collect($query->getAllAliases())->contains('pc')) { - $query = $query->leftJoin("e.promo_code", "pc"); - } - $query = $query->leftJoin("pc.tags", "pct"); - } - if ($filter->hasFilter('view_type_id')) { - $query = $query->join("bt.allowed_view_types", "avt"); - } - if($filter->hasFilter("owner_email")){ - // add all managed tickets too - $query = $query->leftJoin("a.manager", "am"); - $query = $query->leftJoin("am.member", "m2"); + $this->joinCatalog['a'][1] = 'leftJoin'; + $this->joinCatalog['bt'][1] = 'leftJoin'; + $this->joinCatalog['al'][1] = 'leftJoin'; + $this->joinCatalog['ord_m'][1] = 'leftJoin'; + + foreach ($this->requiredAliases($filter, $order) as $alias) { + $this->ensureJoin($query, $alias); } + + return $query; } @@ -581,4 +716,52 @@ public function getAllTicketsIdsByOrder(int $order_id, PagingInfo $paging_info): $res = $query->getQuery()->getArrayResult(); return array_column($res, 'id'); } -} \ No newline at end of file + + + /** + * @param PagingInfo $paging_info + * @param Filter|null $filter + * @param Order|null $order + * @return PagingResponse + */ + public function getAllByPage(PagingInfo $paging_info, Filter $filter = null, Order $order = null){ + $start = time(); + Log::debug(sprintf('DoctrineSummitAttendeeTicketRepository::getAllByPage')); + $total = $this->getFastCount($filter, $order); + if(!$total) return new PagingResponse(0, $paging_info->getPerPage(), $paging_info->getCurrentPage(), 0, []); + $ids = $this->getAllIdsByPage($paging_info, $filter, $order); + $query = $this->getEntityManager()->createQueryBuilder() + ->select('e, a, o, tt, pc, b, bt, a_c, m') + ->from($this->getBaseEntity(), 'e') + ->leftJoin('e.owner', 'a')->addSelect('a') + ->leftJoin('e.order', 'o')->addSelect('o') + ->leftJoin('e.ticket_type', 'tt')->addSelect('tt') + ->leftJoin('e.promo_code', 'pc')->addSelect('pc') + ->leftJoin('e.badge', 'b')->addSelect('b') + ->leftJoin('b.type', 'bt')->addSelect('bt') + ->leftJoin('a.company', 'a_c')->addSelect('a_c') + ->leftJoin('a.member', 'm')->addSelect('m') + ->where('e.id IN (:ids)') + ->setParameter('ids', $ids); + + $rows = $query->getQuery()->getResult(); + $byId = []; + foreach ($rows as $e) $byId[$e->getId()] = $e; + + $data = []; + foreach ($ids as $id) { + if (isset($byId[$id])) $data[] = $byId[$id]; + } + + $end = time() - $start; + Log::debug(sprintf('DoctrineSummitAttendeeTicketRepository::getAllByPage %s seconds', $end)); + return new PagingResponse + ( + $total, + $paging_info->getPerPage(), + $paging_info->getCurrentPage(), + $paging_info->getLastPage($total), + $data + ); + } +} diff --git a/app/Repositories/Summit/DoctrineSummitEventRepository.php b/app/Repositories/Summit/DoctrineSummitEventRepository.php index d432e6886..e14394c08 100644 --- a/app/Repositories/Summit/DoctrineSummitEventRepository.php +++ b/app/Repositories/Summit/DoctrineSummitEventRepository.php @@ -98,20 +98,12 @@ protected function getBaseEntity() return SummitEvent::class; } - /** - * @param PagingInfo $paging_info - * @param Filter|null $filter - * @param Order|null $order - * @return PagingResponse - */ - public function getAllByPage(PagingInfo $paging_info, Filter $filter = null, Order $order = null) - { + private function prepareRegularQuery($query, Filter $filter = null, Order $order = null){ $current_track_id = 0; $current_member_id = 0; if (!is_null($filter)) { - Log::debug(sprintf("DoctrineSummitEventRepository::getAllByPage filter %s", $filter)); // check for dependant filtering $track_id_filter = $filter->getUniqueFilter('track_id'); if (!is_null($track_id_filter)) { @@ -122,12 +114,7 @@ public function getAllByPage(PagingInfo $paging_info, Filter $filter = null, Ord $current_member_id = intval($current_member_id_filter->getValue()); } } - - $query = $this->getEntityManager()->createQueryBuilder() - ->select("e") - ->distinct(true) - ->from($this->getBaseEntity(), "e") - ->leftJoin(Presentation::class, 'p', 'WITH', 'e.id = p.id'); + $query = $query->leftJoin(Presentation::class, 'p', 'WITH', 'e.id = p.id'); if (is_null($order) || !$order->hasOrder("votes_count")) { $query = $query @@ -167,12 +154,19 @@ public function getAllByPage(PagingInfo $paging_info, Filter $filter = null, Ord } } - $shouldPerformRandomOrderingByPage = false; - if (!is_null($order)) { - if ($order->hasOrder("page_random")) { - $shouldPerformRandomOrderingByPage = true; - $order->removeOrder("page_random"); + $can_view_private_events = self::isCurrentMemberOnGroup(IGroup::SummitAdministrators); + + if (!$can_view_private_events) { + $idx = 1; + foreach (self::$forbidden_classes as $forbidden_class) { + $query = $query + ->andWhere("not e INSTANCE OF :forbidden_class" . $idx); + $query->setParameter("forbidden_class" . $idx, $forbidden_class); + $idx++; } + } + + if (!is_null($order)) { $order->apply2Query($query, $this->getOrderMappings()); if (!$order->hasOrder('id')) { $query = $query->addOrderBy("e.id", 'ASC'); @@ -184,28 +178,90 @@ public function getAllByPage(PagingInfo $paging_info, Filter $filter = null, Ord $query = $query->addOrderBy("e.id", 'ASC'); } - $can_view_private_events = self::isCurrentMemberOnGroup(IGroup::SummitAdministrators); + return $query; + } + /** + * @param Filter|null $filter + * @param Order|null $order + * @return int + */ + public function getFastCount(Filter $filter = null, Order $order = null){ - if (!$can_view_private_events) { - $idx = 1; - foreach (self::$forbidden_classes as $forbidden_class) { - $query = $query - ->andWhere("not e INSTANCE OF :forbidden_class" . $idx); - $query->setParameter("forbidden_class" . $idx, $forbidden_class); - $idx++; - } - } + $query = $this->getEntityManager() + ->createQueryBuilder() + ->select('COUNT(DISTINCT e.id)') + ->from($this->getBaseEntity(), "e") + ->distinct(false); + + $query = $this->prepareRegularQuery($query, $filter, null); + + return (int) $query->getQuery()->getSingleScalarResult(); + } + + /** + * @param PagingInfo $paging_info + * @param Filter|null $filter + * @param Order|null $order + * @return array + */ + public function getAllIdsByPage(PagingInfo $paging_info, Filter $filter = null, Order $order = null):array { + + $query = $this->getEntityManager() + ->createQueryBuilder() + ->distinct(true) + ->select("e.id") + ->from($this->getBaseEntity(), "e"); + + $query = $this->prepareRegularQuery($query, $filter, $order); $query = $query ->setFirstResult($paging_info->getOffset()) ->setMaxResults($paging_info->getPerPage()); - $paginator = new Paginator($query, $fetchJoinCollection = true); - $total = $paginator->count(); + $res = $query->getQuery()->getArrayResult(); + return array_column($res, 'id'); + } + /** + * @param PagingInfo $paging_info + * @param Filter|null $filter + * @param Order|null $order + * @return PagingResponse + */ + public function getAllByPage(PagingInfo $paging_info, Filter $filter = null, Order $order = null) + { + + $start = time(); + $shouldPerformRandomOrderingByPage = false; + if (!is_null($order)) { + if ($order->hasOrder("page_random")) { + $shouldPerformRandomOrderingByPage = true; + $order->removeOrder("page_random"); + } + } + Log::debug(sprintf('DoctrineSummitEventRepository::getAllByPage')); + $total = $this->getFastCount($filter, null); + if(!$total) return new PagingResponse(0, $paging_info->getPerPage(), $paging_info->getCurrentPage(), 0, []); + $ids = $this->getAllIdsByPage($paging_info, $filter, $order); + + $query = $this->getEntityManager()->createQueryBuilder() + ->select("e") + ->from($this->getBaseEntity(), "e"); + + $query = $this->prepareRegularQuery($query, $filter, $order) + ->andWhere('e.id IN (:ids)') + ->setParameter('ids', $ids); + + $rows = $query->getQuery()->getResult(); + $byId = []; + foreach ($rows as $e) $byId[$e->getId()] = $e; + $data = []; + foreach ($ids as $id) { + if (isset($byId[$id])) $data[] = $byId[$id]; + } - foreach ($paginator as $entity) - $data[] = $entity; + $end = time() - $start; + Log::debug(sprintf('DoctrineSummitEventRepository::getAllByPage %s seconds', $end)); if ($shouldPerformRandomOrderingByPage) shuffle($data); @@ -381,11 +437,11 @@ protected function getCustomFilterMappings(int $current_member_id, int $current_ 'rejected' => new DoctrineCaseFilterMapping( 'rejected', sprintf('selp is not null AND selp.selection_begin_date is not null AND selp.selection_begin_date <= UTC_TIMESTAMP() AND e.published = 0 AND NOT EXISTS ( - SELECT ___sp31.id + SELECT ___sp31.id FROM models\summit\SummitSelectedPresentation ___sp31 JOIN ___sp31.presentation ___p31 JOIN ___sp31.list ___spl31 WITH ___spl31.list_type = \'%2$s\' AND ___spl31.list_class = \'%3$s\' - WHERE ___p31.id = e.id + WHERE ___p31.id = e.id AND ___sp31.collection = \'%1$s\' )', SummitSelectedPresentation::CollectionSelected, @@ -451,17 +507,17 @@ protected function getCustomFilterMappings(int $current_member_id, int $current_ 'moved', sprintf ( - "not exists + "not exists ( - select vw1 from models\summit\PresentationTrackChairView vw1 + select vw1 from models\summit\PresentationTrackChairView vw1 inner join vw1.presentation p1 join vw1.viewer v1 where p1.id = p.id and v1.id = %s - ) - and exists - ( - select cch from models\summit\SummitCategoryChange cch + ) + and exists + ( + select cch from models\summit\SummitCategoryChange cch inner join cch.presentation p2 - inner join cch.new_category nc - where p2.id = p.id and + inner join cch.new_category nc + where p2.id = p.id and cch.status = %s and nc.id = %s ) ", @@ -510,7 +566,7 @@ protected function getCustomFilterMappings(int $current_member_id, int $current_ */ 'has_media_upload_with_type' => new DoctrineFilterMapping( 'EXISTS ( - SELECT pm1:i.id + SELECT pm1:i.id FROM models\summit\PresentationMediaUpload pm1:i JOIN pm1:i.media_upload_type mut1:i JOIN pm1:i.presentation p3:i @@ -519,7 +575,7 @@ protected function getCustomFilterMappings(int $current_member_id, int $current_ ), 'has_not_media_upload_with_type' => new DoctrineFilterMapping( 'NOT EXISTS ( - SELECT pm2:i.id + SELECT pm2:i.id FROM models\summit\PresentationMediaUpload pm2:i JOIN pm2:i.media_upload_type mut2:i JOIN pm2:i.presentation p4:i @@ -561,10 +617,10 @@ protected function getOrderMappings() 'created_by_company' => 'cb.company', 'speaker_company' => "sp.company", 'level' => << << << ___pc333.session_count + AND ___sp333.order > ___pc333.session_count ) THEN 'alternate' ELSE 'pending' END @@ -633,7 +689,7 @@ protected function getOrderMappings() 'review_status' => 'REVIEW_STATUS(e.id)', 'submission_source' => 'e.submission_source', 'submission_status' => <<getRSVPById($rsvp_id); if (is_null($rsvp)) throw new EntityNotFoundException("RSVP not found."); - + $event = $rsvp->getEvent(); + $member = $rsvp->getOwner(); + if(!is_null($member) && !is_null($event) && $member->isOnSchedule($event)) + $member->removeFromSchedule($event); $this->rsvp_repository->delete($rsvp); }); @@ -576,4 +579,4 @@ function ($root_entity_id) { } ); } -} \ No newline at end of file +} diff --git a/commitlint.config.js b/commitlint.config.js index 2bcf6ba3e..ee4fb8413 100644 --- a/commitlint.config.js +++ b/commitlint.config.js @@ -1,6 +1,6 @@ const WIP_REGEX = /^wip[:]?$/i; const RULE_ERROR_LEVEL = 2; -const HEADER_MAX_LENGTH = 100; +const HEADER_MAX_LENGTH = 150; const SUBJECT_MIN_LENGTH = 5; module.exports = { @@ -73,4 +73,4 @@ module.exports = { } } ] -}; \ No newline at end of file +};