diff --git a/src/AdminHelp.php b/src/AdminHelp.php index d469e576e..51f97a150 100644 --- a/src/AdminHelp.php +++ b/src/AdminHelp.php @@ -175,6 +175,14 @@ protected function participant_fee_amount() { $this->fee(); } + protected function participant_count() { + return '

' . + t('Total number of participants to be registered for this event upon submission of the form.') . + '

' . + t('Note that if a value is not given, the default Participant Count will be 1.') . + '

'; + } + protected function fee() { return '

' . t('Once added to the webform, this field can be configured in a number of ways by changing its settings.') . diff --git a/src/Fields.php b/src/Fields.php index 2202a63fb..9b0f14890 100644 --- a/src/Fields.php +++ b/src/Fields.php @@ -858,6 +858,10 @@ protected function wf_crm_get_fields($var = 'fields') { 'name' => t('Participant Fee'), ] + $moneyDefaults; } + $fields['participant_count'] = [ + 'name' => t('Participant Count'), + 'type' => 'civicrm_number', + ]; } if (isset($sets['membership'])) { $fields['membership_membership_type_id'] = [ diff --git a/src/Utils.php b/src/Utils.php index d7607ea19..35094df5b 100644 --- a/src/Utils.php +++ b/src/Utils.php @@ -198,6 +198,68 @@ function wf_crm_get_events($reg_options, $context) { return $ret; } + /** + * Create a hidden Price Set that contains a Price Field Value with a `count` of one so that `qty` accurately reflects + * number of seats registered on submission + */ + function wf_crm_get_participant_price_set() { + // Get the price_set_value id + $qtyPriceFieldValueId = civicrm_api4('PriceFieldValue', 'get', [ + 'checkPermissions' => FALSE, + 'select' => ['id'], + 'where' => [['name', '=', 'quantity_count_field_value']], + ])[0]['id'] ?? NULL; + // If the Price Set Value exsists, return it. + if ($qtyPriceFieldValueId) { + return $qtyPriceFieldValueId; + } + else { + // If it does not exist, create the price set, price field, and price field value (with a `count` of 1). + $activeFinancialtypeId = civicrm_api4('FinancialType', 'get', [ + 'checkPermissions' => FALSE, + 'select' => ['id'], + 'where' => [['is_active', '=', TRUE]], + 'limit' => 1, + ])[0]['id']; + $qtyPriceSetId = civicrm_api4('PriceSet', 'create', [ + 'checkPermissions' => FALSE, + 'values' => [ + 'name' => 'quantity_count', + 'title' => 'Webform participant quantity count', + 'extends:name' => 'CiviEvent', + 'is_active' => TRUE, + 'is_reserved' => TRUE, + 'is_required' => FALSE, + 'is_quick_config' => TRUE, + 'financial_type_id' => $activeFinancialtypeId, + ], + ])[0]['id']; + $qtyPriceFieldId = civicrm_api4('PriceField', 'create', [ + 'checkPermissions' => FALSE, + 'values' => [ + 'price_set_id' => $qtyPriceSetId, + 'name' => 'quantity_count_field', + 'label' => 'Webform participant quantity count field', + 'html_type' => 'Select', + 'is_active' => TRUE, + ], + ])[0]['id']; + $qtyPriceFieldValueId = civicrm_api4('PriceFieldValue', 'create', [ + 'checkPermissions' => FALSE, + 'values' => [ + 'price_field_id' => $qtyPriceFieldId, + 'name' => 'quantity_count_field_value', + 'label' => 'Webform participant quantity count field value', + 'count' => 1, + 'amount' => 1, + 'is_active' => TRUE, + 'financial_type_id' => $activeFinancialtypeId, + ], + ])[0]['id']; + return $qtyPriceFieldValueId; + } + } + /** * @param array $event * @param string $format diff --git a/src/WebformCivicrmPostProcess.php b/src/WebformCivicrmPostProcess.php index f4f256d9d..e76071e6a 100644 --- a/src/WebformCivicrmPostProcess.php +++ b/src/WebformCivicrmPostProcess.php @@ -478,15 +478,20 @@ private function validateParticipants() { if (is_numeric($eid)) { $this->events[$eid]['ended'] = TRUE; $this->events[$eid]['title'] = t('this event'); - $this->events[$eid]['count'] = wf_crm_aval($this->events, "$eid:count", 0) + $count; + $this->events[$eid]['count'] = wf_crm_aval($this->events, "$eid:count", 0) + ($count * ($p['count'] ?? 1)); + // Get (or create if needed) a Price Set that has a value field that correctly counts the number of participants registered on submission. + $participantPriceValueID = $this->utils->wf_crm_get_participant_price_set(); $this->line_items[] = [ - 'qty' => $count, + 'qty' => $count * ($p['count'] ?? 1), + 'participant_count' => $count * ($p['count'] ?? 1), 'entity_table' => 'civicrm_participant', 'event_id' => $eid, 'contact_ids' => $contacts, - 'unit_price' => $p['fee_amount'] ?? 0, + 'unit_price' => ($p['fee_amount'] ?? 0) / ($p['count'] ?? 1), 'element' => "civicrm_{$c}_participant_{$n}_participant_{$id_and_type}", 'contact_label' => $participantName, + 'price_field_value_id' => $participantPriceValueID, + 'line_total' => $p['fee_amount'] ?? 0, ]; } } @@ -1201,7 +1206,6 @@ private function processParticipants($c, $cid) { if (empty($item['participant_id'])) { $item['participant_id'] = $item['entity_id'] = $result['id']; } - $item['participant_count'] = wf_crm_aval($item, 'participant_count', 0) + 1; break; } } @@ -1216,6 +1220,13 @@ private function processParticipants($c, $cid) { $this->utils->wf_civicrm_api('participant', 'create', ['status_id' => "Cancelled", 'id' => $existing[$eid]]); \Drupal::messenger()->addStatus(t('Registration cancelled for @event', ['@event' => $title])); } + // Free events need line items for correct participant count. + $contribution_enabled = wf_crm_aval($this->data, 'contribution:1:contribution:1:enable_contribution'); + if (!$contribution_enabled && isset($this->line_items)) { + foreach ($this->line_items as $item) { + $this->utils->wf_civicrm_api('lineItem', 'create', $item); + } + } } } } diff --git a/tests/src/FunctionalJavascript/EventTest.php b/tests/src/FunctionalJavascript/EventTest.php index 850eadb06..afac1227b 100644 --- a/tests/src/FunctionalJavascript/EventTest.php +++ b/tests/src/FunctionalJavascript/EventTest.php @@ -140,6 +140,70 @@ function testParticipantContactReference() { $this->assertEquals($contactRef['id'], $participant["{$customKey}_id"]); } + /** + * Verify the participant count is calculated correctly (with multiple participants) + */ + function testParticipantCount() { + $this->drupalLogin($this->adminUser); + $this->drupalGet(Url::fromRoute('entity.webform.civicrm', [ + 'webform' => $this->webform->id(), + ])); + $this->enableCivicrmOnWebform(); + + $event = $this->utils->wf_civicrm_api('Event', 'create', [ + 'event_type_id' => "Conference", + 'title' => "Participant Count Test Event", + 'start_date' => date('Y-m-d'), + 'financial_type_id' => $this->ft['id'], + 'max_participants' => 1000, + ]); + $this->assertEquals(0, $event['is_error']); + $this->assertEquals(1, $event['count']); + + $this->getSession()->getPage()->selectFieldOption('number_of_contacts', 3); + $this->htmlOutput(); + + $this->getSession()->getPage()->clickLink('Event Registration'); + + // Configure Event tab. + $this->getSession()->getPage()->selectFieldOption('participant_reg_type', 'all'); + $this->assertSession()->assertWaitOnAjaxRequest(); + $this->htmlOutput(); + $this->getSession()->getPage()->selectFieldOption('participant_1_number_of_participant', 1); + $this->assertSession()->assertWaitOnAjaxRequest(); + $this->htmlOutput(); + $this->getSession()->getPage()->selectFieldOption('civicrm_1_participant_1_participant_event_id[]', 'Participant Count Test Event'); + $this->getSession()->getPage()->checkField('Participant Fee'); + $this->getSession()->getPage()->checkField('Participant Count'); + + $this->saveCiviCRMSettings(); + + $this->drupalGet($this->webform->toUrl('canonical')); + $this->assertPageNoErrorMessages(); + $edit = [ + 'civicrm_1_contact_1_contact_first_name' => 'Frederick', + 'civicrm_1_contact_1_contact_last_name' => 'Pabst', + 'civicrm_2_contact_1_contact_first_name' => 'Mark', + 'civicrm_2_contact_1_contact_last_name' => 'Anthony', + 'civicrm_3_contact_1_contact_first_name' => 'Emma', + 'civicrm_3_contact_1_contact_last_name' => 'Goldman', + 'civicrm_1_participant_1_participant_fee_amount' => '120', + 'civicrm_1_participant_1_participant_count' => '3', + ]; + $this->postSubmission($this->webform, $edit); + + $eventSeatsAvailable = civicrm_api3('Event', 'get', [ + 'sequential' => 1, + 'return' => ["is_full"], + 'title' => "Participant Count Test Event", + ]); + + $this->assertEquals(994, $eventSeatsAvailable['values'][0]['available_places']); + + // Ensure both contacts are added to the event. + $this->verifyResults(); + } + /** * Event Participant submission. */