From ff9646ecfee5d7bfd934fca75de6b7fdd46b428b Mon Sep 17 00:00:00 2001 From: Eileen McNaughton Date: Mon, 19 Jun 2023 13:25:27 +1200 Subject: [PATCH] Enhance examples to cover additional participants --- CRM/Event/WorkflowMessage/EventExamples.php | 124 ++++++++++- .../WorkflowMessage/ParticipantTrait.php | 194 +++++++++++++++++- .../event_online_receipt_html.tpl | 8 +- 3 files changed, 315 insertions(+), 11 deletions(-) diff --git a/CRM/Event/WorkflowMessage/EventExamples.php b/CRM/Event/WorkflowMessage/EventExamples.php index 556bb42b26..dbbdb6f589 100644 --- a/CRM/Event/WorkflowMessage/EventExamples.php +++ b/CRM/Event/WorkflowMessage/EventExamples.php @@ -1,9 +1,12 @@ 'workflow/' . $workflow . '/' . 'price_set_' . $priceSet['name'], - 'title' => ts('Completed Registration') . ' : ' . $priceSet['title'], + 'title' => ts('Completed Registration') . ($priceSet['is_multiple_registrations'] ? ' ' . ts('primary participant') : '') . ' : ' . $priceSet['title'], 'tags' => ['preview'], 'workflow' => $workflow, 'is_show_line_items' => !$priceSet['is_quick_config'], 'event_id' => $priceSet['event_id'], + 'is_multiple_registrations' => $priceSet['is_multiple_registrations'], + 'is_primary' => TRUE, + 'price_set_id' => $priceSet['id'], ]; + if ($priceSet['is_multiple_registrations']) { + yield [ + 'name' => 'workflow/' . $workflow . '/' . 'price_set_' . $priceSet['name'] . '/' . 'additional', + 'title' => ts('Completed Registration') . ' ' . ts('additional participant') . ' : ' . $priceSet['title'], + 'tags' => ['preview'], + 'workflow' => $workflow, + 'is_show_line_items' => !$priceSet['is_quick_config'], + 'event_id' => $priceSet['event_id'], + 'is_multiple_registrations' => $priceSet['is_multiple_registrations'], + 'is_primary' => FALSE, + 'price_set_id' => $priceSet['id'], + ]; + } } } } @@ -58,10 +86,36 @@ class CRM_Event_WorkflowMessage_EventExamples extends WorkflowMessageExample { * @throws \CRM_Core_Exception * @throws \Civi\API\Exception\UnauthorizedException */ - private function addExampleData(GenericWorkflowMessage $messageTemplate, $example): void { + private function addExampleData(GenericWorkflowMessage $messageTemplate, array $example): void { $messageTemplate->setContact(\Civi\Test::example('entity/Contact/Barb')); $messageTemplate->setEventID($example['event_id']); - $messageTemplate->setContribution(['total_amount' => 50, 'balance_amount' => 20, 'currency' => 'USD']); + $isPrimary = $example['is_primary']; + $primaryParticipantID = 60; + $otherParticipantID = 70; + $isMultipleRegistrations = $example['is_multiple_registrations']; + $participantContacts = [$primaryParticipantID => ['display_name' => 'Cindy Taylor']]; + if ($isMultipleRegistrations) { + $participantContacts[$otherParticipantID] = ['display_name' => 'Melanie Mulder']; + } + + $mockOrder = new CRM_Financial_BAO_Order(); + $mockOrder->setTemplateContributionID(50); + $mockOrder->setPriceSetID($example['price_set_id']); + + foreach (PriceField::get(FALSE)->addWhere('price_set_id', '=', $mockOrder->getPriceSetID())->execute() as $index => $priceField) { + $priceFieldValue = PriceFieldValue::get()->addWhere('price_field_id', '=', $priceField['id'])->execute(); + $this->setLineItem($mockOrder, $priceField, $priceFieldValue->first(), $index, $primaryParticipantID); + if ($isMultipleRegistrations) { + $this->setLineItem($mockOrder, $priceField, $priceFieldValue->last(), $index . '-' . $otherParticipantID, $otherParticipantID); + } + } + $contribution['total_amount'] = $mockOrder->getTotalAmount(); + $contribution['tax_amount'] = $mockOrder->getTotalTaxAmount() ? round($mockOrder->getTotalTaxAmount(), 2) : 0; + $contribution['tax_exclusive_amount'] = $contribution['total_amount'] - $contribution['tax_amount']; + $messageTemplate->setContribution($contribution); + $messageTemplate->setOrder($mockOrder); + $messageTemplate->setParticipantContacts($participantContacts); + $messageTemplate->setParticipant(['id' => $isPrimary ? $primaryParticipantID : $otherParticipantID, 'registered_by_id' => $isPrimary ? NULL : $primaryParticipantID, 'register_date' => date('Y-m-d')]); } /** @@ -72,10 +126,10 @@ class CRM_Event_WorkflowMessage_EventExamples extends WorkflowMessageExample { * @throws \CRM_Core_Exception */ private function getPriceSets(): ?array { - $quickConfigPriceSet = $this->getPriceSet(TRUE); $nonQuickConfigPriceSet = $this->getPriceSet(FALSE); + $quickConfigPriceSet = $this->getPriceSet(TRUE); - return array_filter([$quickConfigPriceSet, $nonQuickConfigPriceSet]); + return array_filter([$nonQuickConfigPriceSet, $quickConfigPriceSet]); } /** @@ -87,20 +141,78 @@ class CRM_Event_WorkflowMessage_EventExamples extends WorkflowMessageExample { * @throws \CRM_Core_Exception */ private function getPriceSet(bool $isQuickConfig): ?array { + // Try to find an event configured for multiple registrations $priceSetEntity = PriceSetEntity::get(FALSE) ->addWhere('entity_table', '=', 'civicrm_event') ->addSelect('price_set_id.id', 'entity_id', 'price_set_id.is_quick_config', 'price_set_id.name', 'price_set_id.title') ->setLimit(1) ->addWhere('price_set_id.is_quick_config', '=', $isQuickConfig) + ->addWhere('entity_id', 'IN', $this->getMultipleRegistrationEventIDs()) ->execute()->first(); + if (empty($priceSetEntity)) { + // Try again without limiting to multiple registrations. + $priceSetEntity = PriceSetEntity::get(FALSE) + ->addWhere('entity_table', '=', 'civicrm_event') + ->addSelect('price_set_id.id', 'entity_id', 'price_set_id.is_quick_config', 'price_set_id.name', 'price_set_id.title') + ->setLimit(1) + ->addWhere('price_set_id.is_quick_config', '=', $isQuickConfig) + ->execute()->first(); + } return empty($priceSetEntity) ? NULL : [ - 'id' => $priceSetEntity['price_set_id'], + 'id' => $priceSetEntity['price_set_id.id'], 'name' => $priceSetEntity['price_set_id.name'], 'title' => $priceSetEntity['price_set_id.title'], 'event_id' => $priceSetEntity['entity_id'], 'is_quick_config' => $priceSetEntity['price_set_id.is_quick_config'], + 'is_multiple_registrations' => in_array($priceSetEntity['entity_id'], $this->getMultipleRegistrationEventIDs(), TRUE), ]; } + /** + * @param \CRM_Financial_BAO_Order $mockOrder + * @param $priceField + * @param array|null $priceFieldValue + * @param int $participantID + * @param $index + * + * @throws \CRM_Core_Exception + */ + private function setLineItem(CRM_Financial_BAO_Order $mockOrder, $priceField, ?array $priceFieldValue, $index, $participantID): void { + $mockOrder->setLineItem([ + 'price_field_id' => $priceField['id'], + 'price_field_id.label' => $priceField['label'], + 'price_field_value_id' => $priceFieldValue['id'], + 'qty' => $priceField['is_enter_qty'] ? 2 : 1, + 'unit_price' => $priceFieldValue['amount'], + 'line_total' => $priceField['is_enter_qty'] ? ($priceFieldValue['amount'] * 2) : $priceFieldValue['amount'], + 'label' => $priceFieldValue['label'], + 'financial_type_id' => $priceFieldValue['financial_type_id'], + 'non_deductible_amount' => $priceFieldValue['non_deductible_amount'], + 'entity_table' => 'civicrm_participant', + 'entity_id' => $participantID, + ], $index); + } + + /** + * Get the ids of (up to 25) recent multiple registration events. + * + * @return array + * @throws \CRM_Core_Exception + */ + private function getMultipleRegistrationEventIDs(): array { + if ($this->multipleRegistrationEventIDs === NULL) { + $this->multipleRegistrationEventIDs = array_keys((array) Event::get(FALSE) + ->addWhere('is_multiple_registrations', '=', TRUE) + ->addWhere('max_additional_participants', '>', 0) + ->addSelect('id') + ->addOrderBy('start_date', 'DESC') + ->setLimit(25) + ->execute() + ->indexBy('id')); + + } + return $this->multipleRegistrationEventIDs; + } + } diff --git a/CRM/Event/WorkflowMessage/ParticipantTrait.php b/CRM/Event/WorkflowMessage/ParticipantTrait.php index 6d9df1d4fe..00b4e20dcf 100644 --- a/CRM/Event/WorkflowMessage/ParticipantTrait.php +++ b/CRM/Event/WorkflowMessage/ParticipantTrait.php @@ -1,10 +1,13 @@ participantContacts = $participantContacts; + return $this; + } + /** * @param int $eventID * * @return CRM_Event_WorkflowMessage_ParticipantTrait */ - public function setEventID(int $eventID) { + public function setEventID(int $eventID): self { $this->eventID = $eventID; return $this; } + /** + * Is the participant the primary participant. + * + * @return bool + * @throws \CRM_Core_Exception + */ + public function getIsPrimary(): bool { + return !$this->getParticipant()['registered_by_id']; + } + + /** + * @return int + */ + public function getPrimaryParticipantID(): int { + return $this->participant['registered_by_id'] ?: $this->participantID; + } + + /** + * Set contribution object. + * + * @param array $participant + * + * @return $this + */ + public function setParticipant(array $participant): self { + $this->participant = $participant; + if (!empty($participant['id'])) { + $this->participantID = $participant['id']; + } + if (!empty($participant['event_id'])) { + $this->eventID = $participant['event_id']; + } + return $this; + } + + /** + * Get the participant record. + * + * @return array + * @throws \CRM_Core_Exception + */ + public function getParticipant(): array { + if (!$this->participant) { + $this->participant = Participant::get(FALSE) + ->addWhere('id', '=', $this->participantID) + ->addSelect('registered_by_id')->execute()->first(); + } + return $this->participant; + } + + /** + * Get the line items and tax information indexed by participant. + * + * We will likely add profile data to this too. This is so we can iterate through + * participants as the primary participant needs to show them all (and the others + * need to be able to filter). + * + * @return array + * @throws \CRM_Core_Exception + */ + public function getParticipants(): array { + if (!$this->participants) { + if (!$this->getLineItems()) { + return []; + } + // Initiate with the current participant to ensure they are first. + $participants = [$this->participantID => ['id' => $this->participantID]]; + foreach ($this->getLineItems() as $lineItem) { + if ($lineItem['entity_table'] === 'civicrm_participant') { + $participantID = $lineItem['entity_id']; + } + else { + // It is not clear if this could ever be true - testing the CiviCRM event + // form shows all line items assigned to participants but we should + // assign to primary if this can occur. + $participantID = $this->getPrimaryParticipantID(); + } + $participants[$participantID]['line_items'][] = $lineItem; + if (!isset($participants[$participantID]['totals'])) { + $participants[$participantID]['totals'] = ['total_amount_exclusive' => 0, 'tax_amount' => 0, 'total_amount_inclusive' => 0]; + } + $participants[$participantID]['totals']['total_amount_exclusive'] += $lineItem['line_total']; + $participants[$participantID]['totals']['tax_amount'] += $lineItem['tax_amount']; + $participants[$participantID]['totals']['total_amount_inclusive'] += ($lineItem['line_total'] + $lineItem['tax_amount']); + if (!isset($participants[$participantID]['tax_rate_breakdown'][$lineItem['tax_rate']])) { + $participants[$participantID]['tax_rate_breakdown'][$lineItem['tax_rate']] = [ + 'amount' => 0, + 'rate' => $lineItem['tax_rate'], + 'percentage' => sprintf('%.2f', $lineItem['tax_rate']), + ]; + } + $participants[$participantID]['tax_rate_breakdown'][$lineItem['tax_rate']]['amount'] += $lineItem['tax_amount']; + } + + $count = 1; + foreach ($participants as $participantID => &$participant) { + $participant['id'] = $participantID; + $participant['index'] = $count; + $participant['contact'] = $this->getParticipantContact($participantID); + foreach ($participant['tax_rate_breakdown'] as $rate => $details) { + if ($details['amount'] === 0.0) { + unset($participant['tax_rate_breakdown'][$rate]); + } + } + if (array_keys($participant['tax_rate_breakdown']) === [0]) { + // If the only tax rate charged is 0% then no tax breakdown is returned. + $participant['tax_rate_breakdown'] = []; + } + $count++; + } + $this->participants = $participants; + } + return $this->participants; + } + + /** + * @param $participantID + * + * @return mixed + * @throws \CRM_Core_Exception + * @throws \Civi\API\Exception\UnauthorizedException + */ + private function getParticipantContact($participantID = NULL) { + if (!$participantID) { + $participantID = $this->participantID; + } + if (empty($this->participantContacts[$participantID])) { + $participantContact = Participant::get(FALSE) + ->addWhere('id', '=', $participantID ?: $this->participantID) + ->addSelect('contact_id.display_name', 'contact_id') + ->execute() + ->first(); + $this->participantContacts[$participantID] = ['id' => $participantContact['contact_id'], 'display_name' => $participantContact['contact_id.display_name']]; + } + + return $this->participantContacts[$participantID]; + } + } diff --git a/xml/templates/message_templates/event_online_receipt_html.tpl b/xml/templates/message_templates/event_online_receipt_html.tpl index 4ee53b192f..0b3046e550 100644 --- a/xml/templates/message_templates/event_online_receipt_html.tpl +++ b/xml/templates/message_templates/event_online_receipt_html.tpl @@ -39,12 +39,12 @@

{if !empty($isOnWaitlist)}

{ts}You have been added to the WAIT LIST for this event.{/ts}

- {if !empty($isPrimary)} + {if $isPrimary}

{ts}If space becomes available you will receive an email with a link to a web page where you can complete your registration.{/ts}

{/if} {elseif !empty($isRequireApproval)}

{ts}Your registration has been submitted.{/ts}

- {if !empty($isPrimary)} + {if $isPrimary}

{ts}Once your registration has been reviewed, you will receive an email with a link to a web page where you can complete the registration process.{/ts}

{/if} {elseif !empty($is_pay_later) && empty($isAmountzero) && empty($isAdditionalParticipant)} @@ -303,7 +303,7 @@ {/if} - {if !empty($isPrimary)} + {if $isPrimary} {ts}Total Amount{/ts} @@ -483,7 +483,7 @@ {ts 1=$selfcancelxfer_time 2=$selfservice_preposition}You may transfer your registration to another participant or cancel your registration up to %1 hours %2 the event.{/ts} {if !empty($totalAmount)}{ts}Cancellations are not refundable.{/ts}{/if}
- {capture assign=selfService}{crmURL p='civicrm/event/selfsvcupdate' q="reset=1&pid=`$participant.id`&{contact.checksum}" h=0 a=1 fe=1}{/capture} + {capture assign=selfService}{crmURL p='civicrm/event/selfsvcupdate' q="reset=1&pid=`$participantID`&{contact.checksum}" h=0 a=1 fe=1}{/capture} {ts}Click here to transfer or cancel your registration.{/ts} -- 2.25.1