From 2996e4ed4378d7e7deee1f318a2fb9ea1536db01 Mon Sep 17 00:00:00 2001 From: Noah Miller Date: Wed, 24 May 2023 17:46:38 -0700 Subject: [PATCH] api4 Activity: add targets & assignees as read-write virtual multi-value fields --- .../Spec/Provider/ActivitySpecProvider.php | 36 +++++++++++++++++-- .../CRM/Activity/Form/Task/PDFTest.php | 10 +++++- tests/phpunit/api/v4/Entity/ActivityTest.php | 35 ++++++++++++++++++ 3 files changed, 77 insertions(+), 4 deletions(-) diff --git a/Civi/Api4/Service/Spec/Provider/ActivitySpecProvider.php b/Civi/Api4/Service/Spec/Provider/ActivitySpecProvider.php index 73e9ebd9e9..5eb148517d 100644 --- a/Civi/Api4/Service/Spec/Provider/ActivitySpecProvider.php +++ b/Civi/Api4/Service/Spec/Provider/ActivitySpecProvider.php @@ -12,6 +12,7 @@ namespace Civi\Api4\Service\Spec\Provider; +use Civi\Api4\Query\Api4SelectQuery; use Civi\Api4\Service\Spec\FieldSpec; use Civi\Api4\Service\Spec\RequestSpec; @@ -45,7 +46,9 @@ class ActivitySpecProvider extends \Civi\Core\Service\AutoService implements Gen $spec->getFieldByName('activity_type_id') ->setDefaultValue(NULL) ->setRequired($action === 'create'); + } + if (in_array($action, ['get', 'create', 'update'], TRUE)) { $field = new FieldSpec('source_contact_id', 'Activity', 'Integer'); $field->setTitle(ts('Source Contact')); $field->setLabel(ts('Added by')); @@ -53,24 +56,29 @@ class ActivitySpecProvider extends \Civi\Core\Service\AutoService implements Gen $field->setRequired($action === 'create'); $field->setFkEntity('Contact'); $field->setInputType('EntityRef'); + $field->setSqlRenderer([__CLASS__, 'renderSqlForActivityContactIds']); $spec->addFieldSpec($field); $field = new FieldSpec('target_contact_id', 'Activity', 'Array'); $field->setTitle(ts('Target Contacts')); - $field->setLabel(ts('With Contact(s)')); - $field->setDescription(ts('Contact(s) involved in this activity.')); + $field->setLabel(ts('With Contacts')); + $field->setDescription(ts('Contacts involved in this activity.')); $field->setFkEntity('Contact'); $field->setInputType('EntityRef'); $field->setInputAttrs(['multiple' => TRUE]); + $field->setSqlRenderer([__CLASS__, 'renderSqlForActivityContactIds']); + $field->addOutputFormatter([__CLASS__, 'formatOutputForMultipleActivityContactIds']); $spec->addFieldSpec($field); $field = new FieldSpec('assignee_contact_id', 'Activity', 'Array'); $field->setTitle(ts('Assignee Contacts')); $field->setLabel(ts('Assigned to')); - $field->setDescription(ts('Contact(s) assigned to this activity.')); + $field->setDescription(ts('Contacts assigned to this activity.')); $field->setFkEntity('Contact'); $field->setInputType('EntityRef'); $field->setInputAttrs(['multiple' => TRUE]); + $field->setSqlRenderer([__CLASS__, 'renderSqlForActivityContactIds']); + $field->addOutputFormatter([__CLASS__, 'formatOutputForMultipleActivityContactIds']); $spec->addFieldSpec($field); } } @@ -82,4 +90,26 @@ class ActivitySpecProvider extends \Civi\Core\Service\AutoService implements Gen return $entity === 'Activity'; } + public static function renderSqlForActivityContactIds(array $field, Api4SelectQuery $query): string { + $contactLinkTypes = [ + 'source_contact_id' => 'Activity Source', + 'target_contact_id' => 'Activity Targets', + 'assignee_contact_id' => 'Activity Assignees', + ]; + $recordTypeId = \CRM_Core_PseudoConstant::getKey( + 'CRM_Activity_BAO_ActivityContact', + 'record_type_id', + $contactLinkTypes[$field['name']]); + return '(SELECT GROUP_CONCAT(`civicrm_activity_contact`.`contact_id`) ' + . 'FROM `civicrm_activity_contact` ' + . 'WHERE `civicrm_activity_contact`.`activity_id` = `a`.`id` ' + . 'AND record_type_id = ' . $recordTypeId . ')'; + } + + public static function formatOutputForMultipleActivityContactIds( + ?string &$value, array $row, array $field + ): void { + $value = explode(',', $value ?? ''); + } + } diff --git a/tests/phpunit/CRM/Activity/Form/Task/PDFTest.php b/tests/phpunit/CRM/Activity/Form/Task/PDFTest.php index 3f0056dd0a..bebdcbf741 100644 --- a/tests/phpunit/CRM/Activity/Form/Task/PDFTest.php +++ b/tests/phpunit/CRM/Activity/Form/Task/PDFTest.php @@ -23,11 +23,13 @@ class CRM_Activity_Form_Task_PDFTest extends CiviUnitTestCase { */ public function testCreateDocumentBasicTokens(): void { CRM_Core_BAO_ConfigSetting::enableComponent('CiviCase'); - $case = $this->createCase($this->individualCreate()); + $sourceContactId = $this->individualCreate(); + $case = $this->createCase($sourceContactId); $activity = $this->activityCreate([ 'campaign_id' => $this->campaignCreate(), 'case_id' => $case->id, + 'source_contact_id' => $sourceContactId, ]); $data = [ ['Subject: {activity.subject}', 'Subject: Discussion on warm beer'], @@ -44,6 +46,9 @@ class CRM_Activity_Form_Task_PDFTest extends CiviUnitTestCase { ['(legacy) Activity ID: {activity.activity_id}', '(legacy) Activity ID: ' . $activity['id']], ['Activity ID: {activity.id}', 'Activity ID: ' . $activity['id']], ['(APIv4 virtual field) Case ID: {activity.case_id}', '(APIv4 virtual field) Case ID: ' . $case->id], + ['(APIv4 virtual field) Source Contact ID: {activity.source_contact_id}', '(APIv4 virtual field) Source Contact ID: ' . $sourceContactId], + ['(APIv4 virtual field) Target Contact IDs: {activity.target_contact_id}', '(APIv4 virtual field) Target Contact IDs: ' . $activity['target_contact_id']], + ['(APIv4 virtual field) Assignee Contact IDs: {activity.assignee_contact_id}', '(APIv4 virtual field) Assignee Contact IDs: ' . $activity['assignee_contact_id']], ]; $tokenProcessor = new TokenProcessor(Civi::dispatcher(), ['schema' => ['activityId']]); @@ -81,6 +86,9 @@ class CRM_Activity_Form_Task_PDFTest extends CiviUnitTestCase { '{activity.status_id:label}' => 'Activity Status', '{activity.campaign_id:label}' => 'Campaign', '{activity.case_id}' => 'Case ID', + '{activity.source_contact_id}' => 'Source Contact', + '{activity.target_contact_id}' => 'Target Contacts', + '{activity.assignee_contact_id}' => 'Assignee Contacts', ]; } diff --git a/tests/phpunit/api/v4/Entity/ActivityTest.php b/tests/phpunit/api/v4/Entity/ActivityTest.php index c0a952ba3d..2c504d849b 100644 --- a/tests/phpunit/api/v4/Entity/ActivityTest.php +++ b/tests/phpunit/api/v4/Entity/ActivityTest.php @@ -100,4 +100,39 @@ class ActivityTest extends Api4TestBase implements TransactionalInterface { $this->assertEquals($expectedActivityContacts, $activityContacts, "ActivityContacts not as expected after update."); } + public function testActivityCreateAndGetVirtualFields() { + $meetingActivityTypeID = \Civi\Api4\OptionValue::get() + ->addSelect('value') + ->addWhere('option_group_id:name', '=', 'activity_type') + ->execute()->first()['value']; + + $sourceContactId = \CRM_Core_BAO_Domain::getDomain()->contact_id; + $c1 = Contact::create(FALSE)->addValue('first_name', '1')->execute()->first()['id']; + $c2 = Contact::create(FALSE)->addValue('first_name', '2')->execute()->first()['id']; + $c3 = Contact::create(FALSE)->addValue('first_name', '3')->execute()->first()['id']; + $c4 = Contact::create(FALSE)->addValue('first_name', '4')->execute()->first()['id']; + + $targetContactIds = [$c1, $c2]; + $assigneeContactIds = [$c3, $c4]; + + // Test that we can write to and read from the virtual fields. + $activityID = Activity::create(FALSE) + ->setValues([ + 'target_contact_id' => $targetContactIds, + 'assignee_contact_id' => $assigneeContactIds, + 'activity_type_id' => $meetingActivityTypeID, + 'source_contact_id' => $sourceContactId, + 'subject' => 'test activity', + ])->execute()->first()['id']; + + $activity = Activity::get(FALSE) + ->addSelect('id', 'source_contact_id', 'target_contact_id', 'assignee_contact_id') + ->addWhere('id', '=', $activityID) + ->execute()->first(); + + $this->assertEquals($sourceContactId, $activity['source_contact_id']); + $this->assertEquals($targetContactIds, $activity['target_contact_id']); + $this->assertEquals($assigneeContactIds, $activity['assignee_contact_id']); + } + } -- 2.25.1