diff --git a/src/AdminHelp.php b/src/AdminHelp.php index d469e576e..b565ccff6 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('Number of participants (for purposes of max participants) this registration counts for 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..a0cc00251 100644 --- a/src/WebformCivicrmPostProcess.php +++ b/src/WebformCivicrmPostProcess.php @@ -478,16 +478,30 @@ 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->line_items[] = [ - 'qty' => $count, - 'entity_table' => 'civicrm_participant', - 'event_id' => $eid, - 'contact_ids' => $contacts, - 'unit_price' => $p['fee_amount'] ?? 0, - 'element' => "civicrm_{$c}_participant_{$n}_participant_{$id_and_type}", - 'contact_label' => $participantName, - ]; + $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(); + // We need a line item for each participant record. + foreach ($contacts as $k => $contact) { + if ($this->data['participant_reg_type'] == 'all') { + $contactIndex = $k; + } + else { + $contactIndex = $c; + } + $this->line_items[] = [ + 'qty' => $p['count'] ?? 1, + 'participant_count' => $p['count'] ?? 1, + 'entity_table' => 'civicrm_participant', + 'event_id' => $eid, + 'contact_ids' => $contact, + 'unit_price' => ($p['fee_amount'] ?? 0) / ($p['count'] ?? 1), + 'element' => "civicrm_{$contactIndex}_participant_{$n}_participant_{$id_and_type}", + 'contact_label' => $participantName, + 'price_field_value_id' => $participantPriceValueID, + 'line_total' => $p['fee_amount'] ?? 0, + ]; + } } } } @@ -1130,6 +1144,7 @@ private function processRelationship($params, $cid1, $cid2) { private function processParticipants($c, $cid) { static $registered_by_id = []; $n = $this->data['participant_reg_type'] == 'separate' ? $c : 1; + $contribution_enabled = wf_crm_aval($this->data, 'contribution:1:contribution:1:enable_contribution'); if ($p = wf_crm_aval($this->data, "participant:$n:participant")) { // Fetch existing participant records $existing = $this->utils->wf_crm_apivalues('Participant', 'get', [ @@ -1197,11 +1212,14 @@ private function processParticipants($c, $cid) { // Update line-item foreach ($this->line_items as &$item) { - if ($item['element'] == "civicrm_{$n}_participant_{$e}_participant_{$id_and_type}") { + if ($item['element'] == "civicrm_{$c}_participant_{$e}_participant_{$id_and_type}") { if (empty($item['participant_id'])) { $item['participant_id'] = $item['entity_id'] = $result['id']; } - $item['participant_count'] = wf_crm_aval($item, 'participant_count', 0) + 1; + // Free events need line items for correct participant count. + if (!$contribution_enabled) { + $this->utils->wf_civicrm_api('lineItem', 'create', $item); + } break; } } diff --git a/tests/src/FunctionalJavascript/EventTest.php b/tests/src/FunctionalJavascript/EventTest.php index 850eadb06..56a57d995 100644 --- a/tests/src/FunctionalJavascript/EventTest.php +++ b/tests/src/FunctionalJavascript/EventTest.php @@ -320,4 +320,102 @@ function testMaxParticipant() { $this->assertSession()->pageTextContains('Test Event 3'); } + /** + * Verify the participant count is calculated correctly (with multiple participants) - register all for same event. + */ + function testParticipantCountSameReg() { + $this->drupalLogin($this->adminUser); + // Set the max participants on the event. + $maxParticipants = 10; + $this->utils->wf_civicrm_api('Event', 'create', ['id' => $this->_event['id'], 'max_participants' => $maxParticipants]); + $this->drupalGet(Url::fromRoute('entity.webform.civicrm', [ + 'webform' => $this->webform->id(), + ])); + $this->enableCivicrmOnWebform(); + + $this->getSession()->getPage()->selectFieldOption('number_of_contacts', 2); + + $this->getSession()->getPage()->clickLink('Event Registration'); + + // Configure Event tab. + $this->getSession()->getPage()->selectFieldOption('participant_reg_type', 'same'); + $this->assertSession()->assertWaitOnAjaxRequest(); + $this->getSession()->getPage()->selectFieldOption('participant_1_number_of_participant', 1); + $this->assertSession()->assertWaitOnAjaxRequest(); + $this->getSession()->getPage()->selectFieldOption('civicrm_1_participant_1_participant_event_id[]', 'Test Event'); + $this->getSession()->getPage()->checkField('Participant Count'); + + $this->saveCiviCRMSettings(); + + // Submit the form. + $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_1_participant_1_participant_count' => '3', + ]; + $this->postSubmission($this->webform, $edit); + // Check the number of places available. + $result = civicrm_api3('Event', 'getsingle', [ + 'return' => ["is_full"], + 'id' => $this->_event['id'], + ]); + // 10 places, 2 participants, count is 3: 10 - (2 * 3) = 4. + $this->assertEquals(4, $result['available_places']); + } + + /** + * Verify the participant count is calculated correctly (with multiple participants) - register for separate events. + */ + function testParticipantCountSeparateReg() { + $this->drupalLogin($this->adminUser); + // Set the max participants on the event. + $maxParticipants = 10; + $this->utils->wf_civicrm_api('Event', 'create', ['id' => $this->_event['id'], 'max_participants' => $maxParticipants]); + $this->drupalGet(Url::fromRoute('entity.webform.civicrm', [ + 'webform' => $this->webform->id(), + ])); + $this->enableCivicrmOnWebform(); + + $this->getSession()->getPage()->selectFieldOption('number_of_contacts', 2); + $this->getSession()->getPage()->clickLink('Event Registration'); + + // Configure Event tab. + $this->getSession()->getPage()->selectFieldOption('participant_reg_type', 'separate'); + $this->assertSession()->assertWaitOnAjaxRequest(); + $this->getSession()->getPage()->selectFieldOption('participant_1_number_of_participant', 1); + $this->assertSession()->assertWaitOnAjaxRequest(); + $this->getSession()->getPage()->selectFieldOption('participant_2_number_of_participant', 1); + $this->assertSession()->assertWaitOnAjaxRequest(); + $this->getSession()->getPage()->selectFieldOption('civicrm_1_participant_1_participant_event_id[]', 'Test Event'); + $this->getSession()->getPage()->selectFieldOption('civicrm_2_participant_1_participant_event_id[]', 'Test Event'); + $this->getSession()->getPage()->checkField('civicrm_1_participant_1_participant_count'); + $this->getSession()->getPage()->checkField('civicrm_2_participant_1_participant_count'); + + $this->saveCiviCRMSettings(); + + // Submit the form. + $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_1_participant_1_participant_count' => '5', + 'civicrm_2_participant_1_participant_count' => '2', + ]; + $this->postSubmission($this->webform, $edit); + // Check the number of places available. + $result = civicrm_api3('Event', 'getsingle', [ + 'return' => ["is_full"], + 'id' => $this->_event['id'], + ]); + // 10 - 5 - 2 = 3. + $this->assertEquals(3, $result['available_places']); + } + }