From 04ef69d3ac75fb8cd3b51738a179d25df0cd5c74 Mon Sep 17 00:00:00 2001 From: Coleman Watts Date: Mon, 4 Oct 2021 12:16:09 -0400 Subject: [PATCH] APIv4 - Add Contact.age_years extra field to make age calculations easy Makes it simple to return or filter on a contact's age --- .../Spec/Provider/ContactGetSpecProvider.php | 23 ++++++++++++++ Civi/Api4/Service/Spec/RequestSpec.php | 31 ++++++++++++++++++- Civi/Api4/Service/Spec/SpecGatherer.php | 25 +++++++-------- .../phpunit/api/v4/Action/ContactGetTest.php | 23 ++++++++++++++ 4 files changed, 87 insertions(+), 15 deletions(-) diff --git a/Civi/Api4/Service/Spec/Provider/ContactGetSpecProvider.php b/Civi/Api4/Service/Spec/Provider/ContactGetSpecProvider.php index e6ff17de27..632dcd3cc1 100644 --- a/Civi/Api4/Service/Spec/Provider/ContactGetSpecProvider.php +++ b/Civi/Api4/Service/Spec/Provider/ContactGetSpecProvider.php @@ -22,6 +22,7 @@ class ContactGetSpecProvider implements Generic\SpecProviderInterface { * @param \Civi\Api4\Service\Spec\RequestSpec $spec */ public function modifySpec(RequestSpec $spec) { + // Groups field $field = new FieldSpec('groups', 'Contact', 'Array'); $field->setLabel(ts('In Groups')) ->setTitle(ts('Groups')) @@ -33,6 +34,19 @@ class ContactGetSpecProvider implements Generic\SpecProviderInterface { ->setSuffixes(['id', 'name', 'label']) ->setOptionsCallback([__CLASS__, 'getGroupList']); $spec->addFieldSpec($field); + + // Age field + if (!$spec->getValue('contact_type') || $spec->getValue('contact_type') === 'Individual') { + $field = new FieldSpec('age_years', 'Contact', 'Integer'); + $field->setLabel(ts('Age (years)')) + ->setTitle(ts('Age (years)')) + ->setColumnName('birth_date') + ->setDescription(ts('Age of individual (in years)')) + ->setType('Extra') + ->setReadonly(TRUE) + ->setSqlRenderer([__CLASS__, 'calculateAge']); + $spec->addFieldSpec($field); + } } /** @@ -94,4 +108,13 @@ class ContactGetSpecProvider implements Generic\SpecProviderInterface { return $options; } + /** + * Generate SQL for age field + * @param array $field + * @return string + */ + public static function calculateAge(array $field) { + return "TIMESTAMPDIFF(YEAR, {$field['sql_name']}, CURDATE())"; + } + } diff --git a/Civi/Api4/Service/Spec/RequestSpec.php b/Civi/Api4/Service/Spec/RequestSpec.php index a66b244a63..d1686c6010 100644 --- a/Civi/Api4/Service/Spec/RequestSpec.php +++ b/Civi/Api4/Service/Spec/RequestSpec.php @@ -36,16 +36,30 @@ class RequestSpec implements \Iterator { */ protected $fields = []; + /** + * @var array + */ + protected $values = []; + /** * @param string $entity * @param string $action + * @param array $values */ - public function __construct($entity, $action) { + public function __construct($entity, $action, $values = []) { $this->entity = $entity; $this->action = $action; $this->entityTableName = CoreUtil::getTableName($entity); + // Set contact_type from id if possible + if ($entity === 'Contact' && empty($values['contact_type']) && !empty($values['id'])) { + $values['contact_type'] = \CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $values['id'], 'contact_id'); + } + $this->values = $values; } + /** + * @param FieldSpec $field + */ public function addFieldSpec(FieldSpec $field) { if (!$field->getEntity()) { $field->setEntity($this->entity); @@ -126,6 +140,21 @@ class RequestSpec implements \Iterator { return $this->entity; } + /** + * @return array + */ + public function getValues() { + return $this->values; + } + + /** + * @param string $key + * @return mixed + */ + public function getValue(string $key) { + return $this->values[$key] ?? NULL; + } + /** * @return string */ diff --git a/Civi/Api4/Service/Spec/SpecGatherer.php b/Civi/Api4/Service/Spec/SpecGatherer.php index f4a9eacd9e..51a36fc82b 100644 --- a/Civi/Api4/Service/Spec/SpecGatherer.php +++ b/Civi/Api4/Service/Spec/SpecGatherer.php @@ -40,7 +40,7 @@ class SpecGatherer { * @return \Civi\Api4\Service\Spec\RequestSpec */ public function getSpec($entity, $action, $includeCustom, $values = []) { - $specification = new RequestSpec($entity, $action); + $specification = new RequestSpec($entity, $action, $values); // Real entities if (strpos($entity, 'Custom_') !== 0) { @@ -80,17 +80,16 @@ class SpecGatherer { /** * @param string $entity * @param string $action - * @param \Civi\Api4\Service\Spec\RequestSpec $specification - * @param array $values + * @param \Civi\Api4\Service\Spec\RequestSpec $spec */ - private function addDAOFields($entity, $action, RequestSpec $specification, $values = []) { + private function addDAOFields($entity, $action, RequestSpec $spec) { $DAOFields = $this->getDAOFields($entity); foreach ($DAOFields as $DAOField) { if ($DAOField['name'] == 'id' && $action == 'create') { continue; } - if (array_key_exists('contactType', $DAOField) && !empty($values['contact_type']) && $DAOField['contactType'] != $values['contact_type']) { + if (array_key_exists('contactType', $DAOField) && $spec->getValue('contact_type') && $DAOField['contactType'] != $spec->getValue('contact_type')) { continue; } if (!empty($DAOField['component']) && @@ -105,28 +104,26 @@ class SpecGatherer { $DAOField['default'] = '1'; } $field = SpecFormatter::arrayToField($DAOField, $entity); - $specification->addFieldSpec($field); + $spec->addFieldSpec($field); } } /** * Get custom fields that extend this entity * - * @see \CRM_Core_SelectValues::customGroupExtends - * * @param string $entity - * @param \Civi\Api4\Service\Spec\RequestSpec $specification - * @param array $values + * @param \Civi\Api4\Service\Spec\RequestSpec $spec * @throws \API_Exception + * @see \CRM_Core_SelectValues::customGroupExtends */ - private function addCustomFields($entity, RequestSpec $specification, $values = []) { + private function addCustomFields($entity, RequestSpec $spec) { $customInfo = \Civi\Api4\Utils\CoreUtil::getCustomGroupExtends($entity); if (!$customInfo) { return; } // If a contact_type was passed in, exclude custom groups for other contact types - if ($entity === 'Contact' && !empty($values['contact_type'])) { - $extends = ['Contact', $values['contact_type']]; + if ($entity === 'Contact' && $spec->getValue('contact_type')) { + $extends = ['Contact', $spec->getValue('contact_type')]; } else { $extends = $customInfo['extends']; @@ -139,7 +136,7 @@ class SpecGatherer { foreach ($customFields as $fieldArray) { $field = SpecFormatter::arrayToField($fieldArray, $entity); - $specification->addFieldSpec($field); + $spec->addFieldSpec($field); } } diff --git a/tests/phpunit/api/v4/Action/ContactGetTest.php b/tests/phpunit/api/v4/Action/ContactGetTest.php index 4d14583e82..c85c96229f 100644 --- a/tests/phpunit/api/v4/Action/ContactGetTest.php +++ b/tests/phpunit/api/v4/Action/ContactGetTest.php @@ -322,4 +322,27 @@ class ContactGetTest extends \api\v4\UnitTestCase { ->execute(); } + public function testAge(): void { + $lastName = uniqid(__FUNCTION__); + $sampleData = [ + ['first_name' => 'abc', 'last_name' => $lastName, 'birth_date' => 'now - 1 year - 1 month'], + ['first_name' => 'def', 'last_name' => $lastName, 'birth_date' => 'now - 21 year - 6 month'], + ]; + Contact::save(FALSE) + ->setRecords($sampleData) + ->execute(); + + $result = Contact::get(FALSE) + ->addWhere('last_name', '=', $lastName) + ->addSelect('first_name', 'age_years') + ->execute()->indexBy('first_name'); + $this->assertEquals(1, $result['abc']['age_years']); + $this->assertEquals(21, $result['def']['age_years']); + + Contact::get(FALSE) + ->addWhere('age_years', '=', 21) + ->addWhere('last_name', '=', $lastName) + ->execute()->single(); + } + } -- 2.25.1