Afform - Add ContactDedupe Behavior
authorColeman Watts <coleman@civicrm.org>
Sun, 18 Sep 2022 20:02:11 +0000 (16:02 -0400)
committerColeman Watts <coleman@civicrm.org>
Sun, 23 Oct 2022 22:46:32 +0000 (18:46 -0400)
This allow existing contacts to be updated per form rules

ext/afform/core/Civi/Afform/Behavior/ContactDedupe.php [new file with mode: 0644]
ext/afform/core/afform.php
ext/afform/mock/tests/phpunit/api/v4/AfformContactUsageTest.php

diff --git a/ext/afform/core/Civi/Afform/Behavior/ContactDedupe.php b/ext/afform/core/Civi/Afform/Behavior/ContactDedupe.php
new file mode 100644 (file)
index 0000000..aec3c24
--- /dev/null
@@ -0,0 +1,76 @@
+<?php
+namespace Civi\Afform\behavior;
+
+use Civi\Afform\AbstractBehavior;
+use Civi\Afform\Event\AfformSubmitEvent;
+use Civi\Api4\Contact;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use CRM_Afform_ExtensionUtil as E;
+
+class ContactDedupe extends AbstractBehavior implements EventSubscriberInterface {
+
+  /**
+   * @return array
+   */
+  public static function getSubscribedEvents() {
+    return [
+      'civi.afform.submit' => ['onAfformSubmit', 101],
+    ];
+  }
+
+  public static function getEntities():array {
+    return \CRM_Contact_BAO_ContactType::basicTypes();
+  }
+
+  public static function getTitle():string {
+    return E::ts('Duplicate Matching');
+  }
+
+  public static function getDescription():string {
+    return E::ts('Update existing contact instead of creating a new one based on a dedupe rule.');
+  }
+
+  public static function getModes(string $entityName):array {
+    $modes = [];
+    $dedupeRuleGroups = \Civi\Api4\DedupeRuleGroup::get(FALSE)
+      ->addWhere('contact_type', '=', $entityName)
+      ->addOrderBy('used', 'DESC')
+      ->addOrderBy('title', 'ASC')
+      ->execute();
+    foreach ($dedupeRuleGroups as $rule) {
+      // Use the generic API name for supervised/unsupervised rules as it's more portable
+      $name = ($rule['used'] === 'General' ? $rule['name'] : $entityName . '.' . $rule['used']);
+      $modes[] = [
+        'name' => $name,
+        'label' => $rule['title'],
+      ];
+    }
+    return $modes;
+  }
+
+  public static function onAfformSubmit(AfformSubmitEvent $event) {
+    $entity = $event->getEntity();
+    $dedupeMode = $entity['contact-dedupe'] ?? NULL;
+    if ($event->getEntityType() !== 'Contact' || !$dedupeMode) {
+      return;
+    }
+    // Apply dedupe rule if contact isn't already identified
+    foreach ($event->records as $index => $record) {
+      $supportedJoins = ['Address', 'Email', 'Phone', 'IM'];
+      $values = $record['fields'] ?? [];
+      foreach ($supportedJoins as $joinEntity) {
+        if (!empty($record['joins'][$joinEntity][0])) {
+          $values += \CRM_Utils_Array::prefixKeys($record['joins'][$joinEntity][0], strtolower($joinEntity) . '_primary.');
+        }
+      }
+      $match = Contact::getDuplicates(FALSE)
+        ->setValues($values)
+        ->setDedupeRule($dedupeMode)
+        ->execute()->first();
+      if (!empty($match['id'])) {
+        $event->setEntityId($index, $match['id']);
+      }
+    }
+  }
+
+}
index c06facd1e81da51cae11d57ba5e14d31f813977d..c546334c83f1c086499336835951be3f4e98a863 100644 (file)
@@ -58,6 +58,7 @@ function afform_civicrm_config(&$config) {
   $dispatcher->addListener('civi.afform.get', ['\Civi\Api4\Action\Afform\Get', 'getCustomGroupBlocks']);
   $dispatcher->addSubscriber(new \Civi\Api4\Subscriber\AutocompleteSubscriber());
   $dispatcher->addSubscriber(new \Civi\Afform\Behavior\ContactAutofill());
+  $dispatcher->addSubscriber(new \Civi\Afform\Behavior\ContactDedupe());
 
   // Register support for email tokens
   if (CRM_Extension_System::singleton()->getMapper()->isActiveModule('authx')) {
index 4d39f297715006f4349641f0f5fdbe16184567da..f338f5980ee46be269bdde6aa496e4ab54b03c61 100644 (file)
@@ -355,4 +355,59 @@ EOHTML;
     $this->assertEquals($locationType, $submission['data']['Organization1'][0]['_joins']['Email'][0]['location_type_id']);
   }
 
+  public function testDedupeIndividual(): void {
+    $layout = <<<EOHTML
+<af-form ctrl="modelListCtrl">
+  <af-entity type="Contact" data="{contact_type: 'Individual'}" name="Individual1" contact-dedupe="Individual.Supervised" />
+  <fieldset af-fieldset="Individual1">
+      <af-field name="first_name" />
+      <af-field name="middle_name" />
+      <af-field name="last_name" />
+      <div af-join="Email" min="1" af-repeat="Add">
+        <afblock-contact-email></afblock-contact-email>
+      </div>
+  </fieldset>
+</af-form>
+EOHTML;
+    $this->useValues([
+      'layout' => $layout,
+      'permission' => CRM_Core_Permission::ALWAYS_ALLOW_PERMISSION,
+    ]);
+
+    $lastName = uniqid(__FUNCTION__);
+    $contact = \Civi\Api4\Contact::create(FALSE)
+      ->addValue('first_name', 'Bob')
+      ->addValue('last_name', $lastName)
+      ->addValue('email_primary.email', '123@example.com')
+      ->execute()->single();
+
+    $locationType = CRM_Core_BAO_LocationType::getDefault()->id;
+    $values = [
+      'Individual1' => [
+        [
+          'fields' => [
+            'first_name' => 'Bob',
+            'middle_name' => 'New',
+            'last_name' => $lastName,
+          ],
+          'joins' => [
+            'Email' => [
+              ['email' => '123@example.com', 'location_type_id' => $locationType],
+            ],
+          ],
+        ],
+      ],
+    ];
+    Civi\Api4\Afform::submit()
+      ->setName($this->formName)
+      ->setValues($values)
+      ->execute();
+
+    // Check that the contact was updated per dedupe rule
+    $result = \Civi\Api4\Contact::get(FALSE)
+      ->addWhere('id', '=', $contact['id'])
+      ->execute()->single();
+    $this->assertEquals('New', $result['middle_name']);
+  }
+
 }