Simplify handling of soft credit
authorEileen McNaughton <emcnaughton@wikimedia.org>
Thu, 26 May 2022 00:37:29 +0000 (12:37 +1200)
committerEileen McNaughton <emcnaughton@wikimedia.org>
Wed, 1 Jun 2022 04:16:43 +0000 (16:16 +1200)
CRM/Contribute/BAO/Contribution.php
CRM/Contribute/Import/Form/MapField.php
CRM/Contribute/Import/Form/Preview.php
CRM/Contribute/Import/Parser/Contribution.php
CRM/Import/Parser.php
tests/phpunit/CRM/Contribute/Import/Parser/ContributionTest.php

index 29d6fc1ba65e2ef000bae2a00f709fdd35e7bb33..800c28ac9437b644f500c9fbc3b9e89729ace6e1 100644 (file)
@@ -753,7 +753,7 @@ class CRM_Contribute_BAO_Contribution extends CRM_Contribute_DAO_Contribution im
 
       $tmpContactField['external_identifier'] = $contactFields['external_identifier'];
       $tmpContactField['external_identifier']['title'] = $contactFields['external_identifier']['title'] . ' ' . ts('(match to contact)');
-      $tmpFields['contribution_contact_id']['title'] = $tmpFields['contribution_contact_id']['title'] . ' ' . ts('(match to contact)');
+      $tmpFields['contribution_contact_id']['title'] = $tmpFields['contribution_contact_id']['html']['label'] = $tmpFields['contribution_contact_id']['title'] . ' ' . ts('(match to contact)');
       $fields = array_merge($fields, $tmpContactField);
       $fields = array_merge($fields, $tmpFields);
       $fields = array_merge($fields, $note);
index 61db6eaa4ddd14d2e4b6c9054cc68b980601a6de..78e6a869d8b18122f47005a2dccba902d0f58baf 100644 (file)
@@ -166,7 +166,6 @@ class CRM_Contribute_Import_Form_MapField extends CRM_Import_Form_MapField {
     foreach ($mapperKeys as $key) {
       $this->_fieldUsed[$key] = FALSE;
     }
-    $this->_location_types = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id');
     $sel1 = $this->_mapperFields;
 
     if (!$this->get('onDuplicate')) {
@@ -420,8 +419,9 @@ class CRM_Contribute_Import_Form_MapField extends CRM_Import_Form_MapField {
       $this->controller->resetPage($this->_name);
       return;
     }
+    $this->updateUserJobMetadata('submitted_values', $this->getSubmittedValues());
 
-    $mapper = $mapperKeys = $mapperKeysMain = $mapperSoftCredit = $softCreditFields = $mapperPhoneType = $mapperSoftCreditType = [];
+    $mapper = $mapperKeysMain = $mapperSoftCredit = $softCreditFields = $mapperPhoneType = $mapperSoftCreditType = [];
     $mapperKeys = $this->controller->exportValue($this->_name, 'mapper');
 
     $softCreditTypes = CRM_Core_OptionGroup::values('soft_credit_type');
@@ -478,7 +478,8 @@ class CRM_Contribute_Import_Form_MapField extends CRM_Import_Form_MapField {
       $this->set('savedMapping', $saveMapping->id);
     }
 
-    $parser = new CRM_Contribute_Import_Parser_Contribution($mapperKeysMain, $mapperSoftCredit, $mapperPhoneType);
+    $parser = new CRM_Contribute_Import_Parser_Contribution($mapperKeysMain);
+    $parser->setUserJobID($this->getUserJobID());
     $parser->run(
       $this->getSubmittedValue('uploadFile'),
       $this->getSubmittedValue('fieldSeparator'),
index f496b167b9ad524c35374f74d815a013eda2002c..6d2927bcf248d4808b6590f74157cf864323ed69 100644 (file)
@@ -69,28 +69,13 @@ class CRM_Contribute_Import_Form_Preview extends CRM_Import_Form_Preview {
    */
   public function postProcess() {
     $fileName = $this->controller->exportValue('DataSource', 'uploadFile');
-    $invalidRowCount = $this->get('invalidRowCount');
     $onDuplicate = $this->get('onDuplicate');
-    $mapperSoftCreditType = $this->get('mapperSoftCreditType');
-
+    $this->updateUserJobMetadata('submitted_values', $this->getSubmittedValues());
     $mapper = $this->controller->exportValue('MapField', 'mapper');
-    $mapperKeys = [];
-    $mapperSoftCredit = [];
-    $mapperPhoneType = [];
 
-    foreach ($mapper as $key => $value) {
-      $mapperKeys[$key] = $mapper[$key][0];
-      if (isset($mapper[$key][0]) && $mapper[$key][0] == 'soft_credit' && isset($mapper[$key])) {
-        $mapperSoftCredit[$key] = $mapper[$key][1] ?? '';
-        $mapperSoftCreditType[$key] = $mapperSoftCreditType[$key]['value'];
-      }
-      else {
-        $mapperSoftCredit[$key] = $mapperSoftCreditType[$key] = NULL;
-      }
-    }
-
-    $parser = new CRM_Contribute_Import_Parser_Contribution($mapperKeys, $mapperSoftCredit, $mapperPhoneType, $mapperSoftCreditType);
+    $parser = new CRM_Contribute_Import_Parser_Contribution();
     $parser->setUserJobID($this->getUserJobID());
+
     $mapFields = $this->get('fields');
 
     foreach ($mapper as $key => $value) {
index 0daba3335ea5e7d0a6640dfa10a78233e9002a3c..22ade85dd4ba43a79efd9d9c6df0c98904d5bd71 100644 (file)
@@ -15,6 +15,9 @@
  * @copyright CiviCRM LLC https://civicrm.org/licensing
  */
 
+use Civi\Api4\Contact;
+use Civi\Api4\Email;
+
 /**
  * Class to parse contribution csv files.
  */
@@ -22,11 +25,6 @@ class CRM_Contribute_Import_Parser_Contribution extends CRM_Import_Parser {
 
   protected $_mapperKeys;
 
-  private $_contactIdIndex;
-
-  protected $_mapperSoftCredit;
-  //protected $_mapperPhoneType;
-
   /**
    * Array of successfully imported contribution id's
    *
@@ -38,15 +36,10 @@ class CRM_Contribute_Import_Parser_Contribution extends CRM_Import_Parser {
    * Class constructor.
    *
    * @param $mapperKeys
-   * @param array $mapperSoftCredit
-   * @param null $mapperPhoneType
-   * @param array $mapperSoftCreditType
    */
-  public function __construct(&$mapperKeys = [], $mapperSoftCredit = [], $mapperPhoneType = NULL, $mapperSoftCreditType = []) {
+  public function __construct($mapperKeys = []) {
     parent::__construct();
-    $this->_mapperKeys = &$mapperKeys;
-    $this->_mapperSoftCredit = &$mapperSoftCredit;
-    $this->_mapperSoftCreditType = &$mapperSoftCreditType;
+    $this->_mapperKeys = $mapperKeys;
   }
 
   /**
@@ -164,19 +157,9 @@ class CRM_Contribute_Import_Parser_Contribution extends CRM_Import_Parser {
       throw new CRM_Core_Exception('Unable to determine import file');
     }
     $fileName = $fileName['name'];
-
-    switch ($contactType) {
-      case self::CONTACT_INDIVIDUAL:
-        $this->_contactType = 'Individual';
-        break;
-
-      case self::CONTACT_HOUSEHOLD:
-        $this->_contactType = 'Household';
-        break;
-
-      case self::CONTACT_ORGANIZATION:
-        $this->_contactType = 'Organization';
-    }
+    // Since $this->_contactType is still being called directly do a get call
+    // here to make sure it is instantiated.
+    $this->getContactType();
 
     $this->init();
 
@@ -405,65 +388,24 @@ class CRM_Contribute_Import_Parser_Contribution extends CRM_Import_Parser {
   }
 
   /**
-   * Store the soft credit field information.
-   *
-   * This  was perhaps done this way on the believe that a lot of code pain
-   * was worth it to avoid negligible-cost array iterations. Perhaps we could prioritise
-   * readability & maintainability next since we can just work with functions to retrieve
-   * data from the metadata.
+   * Get the field mappings for the import.
    *
-   * @param array $elements
-   */
-  public function setActiveFieldSoftCredit($elements) {
-    foreach ((array) $elements as $i => $element) {
-      $this->_activeFields[$i]->_softCreditField = $element;
-    }
-  }
-
-  /**
-   * Store the soft credit field type information.
-   *
-   * This  was perhaps done this way on the believe that a lot of code pain
-   * was worth it to avoid negligible-cost array iterations. Perhaps we could prioritise
-   * readability & maintainability next since we can just work with functions to retrieve
-   * data from the metadata.
-   *
-   * @param array $elements
-   */
-  public function setActiveFieldSoftCreditType($elements) {
-    foreach ((array) $elements as $i => $element) {
-      $this->_activeFields[$i]->_softCreditType = $element;
-    }
-  }
-
-  /**
-   * Format the field values for input to the api.
+   * This is the same format as saved in civicrm_mapping_field except
+   * that location_type_id = 'Primary' rather than empty where relevant.
+   * Also 'im_provider_id' is mapped to the 'real' field name 'provider_id'
    *
    * @return array
-   *   (reference ) associative array of name/value pairs
+   * @throws \API_Exception
    */
-  public function &getActiveFieldParams() {
-    $params = [];
-    for ($i = 0; $i < $this->_activeFieldCount; $i++) {
-      if (isset($this->_activeFields[$i]->_value)) {
-        if (isset($this->_activeFields[$i]->_softCreditField)) {
-          if (!isset($params[$this->_activeFields[$i]->_name])) {
-            $params[$this->_activeFields[$i]->_name] = [];
-          }
-          $params[$this->_activeFields[$i]->_name][$i][$this->_activeFields[$i]->_softCreditField] = $this->_activeFields[$i]->_value;
-          if (isset($this->_activeFields[$i]->_softCreditType)) {
-            $params[$this->_activeFields[$i]->_name][$i]['soft_credit_type_id'] = $this->_activeFields[$i]->_softCreditType;
-          }
-        }
-
-        if (!isset($params[$this->_activeFields[$i]->_name])) {
-          if (!isset($this->_activeFields[$i]->_softCreditField)) {
-            $params[$this->_activeFields[$i]->_name] = $this->_activeFields[$i]->_value;
-          }
-        }
-      }
+  protected function getFieldMappings(): array {
+    $mappedFields = [];
+    foreach ($this->getSubmittedValue('mapper') as $i => $mapperRow) {
+      $mappedField = $this->getMappingFieldFromMapperInput($mapperRow, 0, $i);
+      // Just for clarity since 0 is a pseudo-value
+      unset($mappedField['mapping_id']);
+      $mappedFields[] = $mappedField;
     }
-    return $params;
+    return $mappedFields;
   }
 
   /**
@@ -695,22 +637,6 @@ class CRM_Contribute_Import_Parser_Contribution extends CRM_Import_Parser {
     $this->_newContributions = [];
 
     $this->setActiveFields($this->_mapperKeys);
-    $this->setActiveFieldSoftCredit($this->_mapperSoftCredit);
-    $this->setActiveFieldSoftCreditType($this->_mapperSoftCreditType);
-
-    // FIXME: we should do this in one place together with Form/MapField.php
-    $this->_contactIdIndex = -1;
-
-    $index = 0;
-    foreach ($this->_mapperKeys as $key) {
-      switch ($key) {
-        case 'contribution_contact_id':
-          $this->_contactIdIndex = $index;
-          break;
-
-      }
-      $index++;
-    }
   }
 
   /**
@@ -766,9 +692,7 @@ class CRM_Contribute_Import_Parser_Contribution extends CRM_Import_Parser {
    *   CRM_Import_Parser::VALID or CRM_Import_Parser::ERROR
    */
   public function summary(&$values) {
-    $this->setActiveFieldValues($values);
-
-    $params = $this->getActiveFieldParams();
+    $params = $this->getMappedRow($values);
 
     //for date-Formats
     $errorMessage = implode('; ', $this->formatDateFields($params));
@@ -814,7 +738,7 @@ class CRM_Contribute_Import_Parser_Contribution extends CRM_Import_Parser {
       return CRM_Import_Parser::ERROR;
     }
 
-    $params = &$this->getActiveFieldParams();
+    $params = $this->getMappedRow($values);
     $formatted = ['version' => 3, 'skipRecentView' => TRUE, 'skipCleanMoney' => FALSE];
 
     //CRM-10994
@@ -842,9 +766,6 @@ class CRM_Contribute_Import_Parser_Contribution extends CRM_Import_Parser {
     ) {
       $paramValues['contact_type'] = $this->_contactType;
     }
-    elseif (!empty($params['soft_credit'])) {
-      $paramValues['contact_type'] = $this->_contactType;
-    }
     elseif (!empty($paramValues['pledge_payment'])) {
       $paramValues['contact_type'] = $this->_contactType;
     }
@@ -853,7 +774,14 @@ class CRM_Contribute_Import_Parser_Contribution extends CRM_Import_Parser {
     if (!empty($paramValues['pledge_payment'])) {
       $paramValues['onDuplicate'] = $onDuplicate;
     }
-    $formatError = $this->deprecatedFormatParams($paramValues, $formatted, TRUE, $onDuplicate);
+    try {
+      $formatError = $this->deprecatedFormatParams($paramValues, $formatted, TRUE, $onDuplicate);
+    }
+    catch (CRM_Core_Exception $e) {
+      array_unshift($values, $e->getMessage());
+      $errorMapping = ['soft_credit' => self::SOFT_CREDIT_ERROR, 'pledge_payment' => self::PLEDGE_PAYMENT_ERROR];
+      return $errorMapping[$e->getErrorCode()] ?? CRM_Import_Parser::ERROR;
+    }
 
     if ($formatError) {
       array_unshift($values, $formatError['error_message']);
@@ -950,7 +878,7 @@ class CRM_Contribute_Import_Parser_Contribution extends CRM_Import_Parser {
       }
     }
 
-    if ($this->_contactIdIndex < 0) {
+    if (empty($formatted['contact_id'])) {
 
       $error = $this->checkContactDuplicate($paramValues);
 
@@ -1208,6 +1136,7 @@ class CRM_Contribute_Import_Parser_Contribution extends CRM_Import_Parser {
    * @param int $onDuplicate
    *
    * @return array|CRM_Error
+   * @throws \CRM_Core_Exception
    */
   private function deprecatedFormatParams($params, &$values, $create = FALSE, $onDuplicate = NULL) {
     require_once 'CRM/Utils/DeprecatedUtils.php';
@@ -1252,7 +1181,7 @@ class CRM_Contribute_Import_Parser_Contribution extends CRM_Import_Parser {
       }
 
       switch ($key) {
-        case 'contribution_contact_id':
+        case 'contact_id':
           if (!CRM_Utils_Rule::integer($value)) {
             return civicrm_api3_create_error("contact_id not valid: $value");
           }
@@ -1267,9 +1196,7 @@ class CRM_Contribute_Import_Parser_Contribution extends CRM_Import_Parser {
           elseif ($svq == 1) {
             return civicrm_api3_create_error("Invalid Contact ID: contact_id $value is a soft-deleted contact.");
           }
-
-          $values['contact_id'] = $values['contribution_contact_id'];
-          unset($values['contribution_contact_id']);
+          $values['contact_id'] = $value;
           break;
 
         case 'contact_type':
@@ -1369,58 +1296,11 @@ class CRM_Contribute_Import_Parser_Contribution extends CRM_Import_Parser {
         case 'soft_credit':
           // import contribution record according to select contact type
           // validate contact id and external identifier.
-          $value[$key] = $mismatchContactType = $softCreditContactIds = '';
-          if (isset($params[$key]) && is_array($params[$key])) {
-            foreach ($params[$key] as $softKey => $softParam) {
-              $contactId = $softParam['contact_id'] ?? NULL;
-              $externalId = $softParam['external_identifier'] ?? NULL;
-              $email = $softParam['email'] ?? NULL;
-              if ($contactId || $externalId) {
-                require_once 'CRM/Contact/DAO/Contact.php';
-                $contact = new CRM_Contact_DAO_Contact();
-                $contact->id = $contactId;
-                $contact->external_identifier = $externalId;
-                $errorMsg = NULL;
-                if (!$contact->find(TRUE)) {
-                  $field = $contactId ? ts('Contact ID') : ts('External ID');
-                  $errorMsg = ts("Soft Credit %1 - %2 doesn't exist. Row was skipped.",
-                    [1 => $field, 2 => $contactId ? $contactId : $externalId]);
-                }
-
-                if ($errorMsg) {
-                  return civicrm_api3_create_error($errorMsg);
-                }
-
-                // finally get soft credit contact id.
-                $values[$key][$softKey] = $softParam;
-                $values[$key][$softKey]['contact_id'] = $contact->id;
-              }
-              elseif ($email) {
-                if (!CRM_Utils_Rule::email($email)) {
-                  return civicrm_api3_create_error("Invalid email address $email provided for Soft Credit. Row was skipped");
-                }
-
-                // get the contact id from duplicate contact rule, if more than one contact is returned
-                // we should return error, since current interface allows only one-one mapping
-                $emailParams = [
-                  'email' => $email,
-                  'contact_type' => $params['contact_type'],
-                ];
-                $checkDedupe = _civicrm_api3_deprecated_duplicate_formatted_contact($emailParams);
-                if (!$checkDedupe['is_error']) {
-                  return civicrm_api3_create_error("Invalid email address(doesn't exist) $email for Soft Credit. Row was skipped");
-                }
-                $matchingContactIds = explode(',', $checkDedupe['error_message']['params'][0]);
-                if (count($matchingContactIds) > 1) {
-                  return civicrm_api3_create_error("Invalid email address(duplicate) $email for Soft Credit. Row was skipped");
-                }
-                if (count($matchingContactIds) == 1) {
-                  $contactId = $matchingContactIds[0];
-                  unset($softParam['email']);
-                  $values[$key][$softKey] = $softParam + ['contact_id' => $contactId];
-                }
-              }
-            }
+          foreach ($value as $softKey => $softParam) {
+            $values['soft_credit'][$softKey] = [
+              'contact_id' => $this->lookupMatchingContact($softParam),
+              'soft_credit_type_id' => $softParam['soft_credit_type_id'],
+            ];
           }
           break;
 
@@ -1593,8 +1473,6 @@ class CRM_Contribute_Import_Parser_Contribution extends CRM_Import_Parser {
    * @throws \API_Exception
    */
   public function getMappingFieldFromMapperInput(array $fieldMapping, int $mappingID, int $columnNumber): array {
-    $isRelationshipField = preg_match('/\d*_a_b|b_a$/', $fieldMapping[0]);
-    $fieldName = $isRelationshipField ? $fieldMapping[1] : $fieldMapping[0];
     return [
       'name' => $fieldMapping[0],
       'mapping_id' => $mappingID,
@@ -1609,4 +1487,56 @@ class CRM_Contribute_Import_Parser_Contribution extends CRM_Import_Parser {
     ];
   }
 
+  /**
+   * Lookup matching contact.
+   *
+   * This looks up the matching contact from the contact id, external identifier
+   * or email. For the email a straight email search is done - this is equivalent
+   * to what happens on a dedupe rule lookup when the only field is 'email' - but
+   * we can't be sure the rule is 'just email' - and we are not collecting the
+   * fields for any other lookup in the case of soft credits (if we
+   * extend this function to main-contact-lookup we can handle full dedupe
+   * lookups - but note the error messages will need tweaking.
+   *
+   * @param array $params
+   *
+   * @return int
+   *   Contact ID
+   *
+   * @throws \API_Exception
+   * @throws \CRM_Core_Exception
+   */
+  private function lookupMatchingContact(array $params): int {
+    $lookupField = !empty($params['contact_id']) ? 'contact_id' : (!empty($params['external_identifier']) ? 'external_identifier' : 'email');
+    if (empty($params['email'])) {
+      $contact = Contact::get(FALSE)->addSelect('id')
+        ->addWhere($lookupField, '=', $params[$lookupField])
+        ->execute();
+      if (count($contact) !== 1) {
+        throw new CRM_Core_Exception(ts("Soft Credit %1 - %2 doesn't exist. Row was skipped.",
+          [
+            1 => $this->getFieldMetadata($lookupField),
+            2 => $params['contact_id'] ?? $params['external_identifier'],
+          ]));
+      }
+      return $contact->first()['id'];
+    }
+
+    if (!CRM_Utils_Rule::email($params['email'])) {
+      throw new CRM_Core_Exception(ts('Invalid email address %1 provided for Soft Credit. Row was skipped'), [1 => $params['email']]);
+    }
+    $emails = Email::get(FALSE)
+      ->addWhere('contact_id.is_deleted', '=', 0)
+      ->addWhere('contact_id.contact_type', '=', $this->getContactType())
+      ->addWhere('email', '=', $params['email'])
+      ->addSelect('contact_id')->execute();
+    if (count($emails) === 0) {
+      throw new CRM_Core_Exception(ts("Invalid email address(doesn't exist) %1 for Soft Credit. Row was skipped", [1 => $params['email']]));
+    }
+    if (count($emails) > 1) {
+      throw new CRM_Core_Exception(ts('Invalid email address(duplicate) %1 for Soft Credit. Row was skipped', [1 => $params['email']]));
+    }
+    return $emails->first()['contact_id'];
+  }
+
 }
index 4ea35b98c4fb7084b885a4e472fdce18f2ec61b8..bf05a501353bc4669310d6667aad970e249ea69d 100644 (file)
@@ -694,7 +694,7 @@ abstract class CRM_Import_Parser {
    */
   protected function checkContactDuplicate(&$formatValues) {
     //retrieve contact id using contact dedupe rule
-    $formatValues['contact_type'] = $formatValues['contact_type'] ?? $this->_contactType;
+    $formatValues['contact_type'] = $formatValues['contact_type'] ?? $this->getContactType();
     $formatValues['version'] = 3;
     require_once 'CRM/Utils/DeprecatedUtils.php';
     $params = $formatValues;
@@ -723,7 +723,7 @@ abstract class CRM_Import_Parser {
       }
       // CRM-17040, Considering only primary contact when importing contributions. So contribution inserts into primary contact
       // instead of soft credit contact.
-      if (is_array($field) && $key != "soft_credit") {
+      if (is_array($field) && $key !== "soft_credit") {
         foreach ($field as $value) {
           $break = FALSE;
           if (is_array($value)) {
index d36035222525f2ef87d981389bab9e51ca06cd7f..7fabd10ac67825835e24bbfe2ae955dd2fd270a0 100644 (file)
@@ -7,6 +7,7 @@
 use Civi\Api4\Contribution;
 use Civi\Api4\ContributionSoft;
 use Civi\Api4\OptionValue;
+use Civi\Api4\UserJob;
 
 /**
  *  Test Contribution import parser.
@@ -68,9 +69,13 @@ class CRM_Contribute_Import_Parser_ContributionTest extends CiviUnitTestCase {
       'external_identifier' => 'ext-1',
       'soft_credit' => 'ext-2',
     ];
-    $mapperSoftCredit = [NULL, NULL, NULL, 'external_identifier'];
-    $mapperSoftCreditType = [NULL, NULL, NULL, '1'];
-    $this->runImport($values, CRM_Import_Parser::DUPLICATE_UPDATE, CRM_Contribute_Import_Parser_Contribution::SOFT_CREDIT, $mapperSoftCredit, NULL, $mapperSoftCreditType);
+    $mapping = [
+      ['name' => 'total_amount'],
+      ['name' => 'financial_type_id'],
+      ['name' => 'external_identifier'],
+      ['name' => 'soft_credit', 'soft_credit_type_id' => 1, 'soft_credit_match_field' => 'external_identifier'],
+    ];
+    $this->runImport($values, CRM_Import_Parser::DUPLICATE_UPDATE, CRM_Contribute_Import_Parser_Contribution::SOFT_CREDIT, $mapping);
 
     $contributionsOfMainContact = Contribution::get()->addWhere('contact_id', '=', $contact1Id)->execute();
     $this->assertCount(1, $contributionsOfMainContact, 'Contribution not added for primary contact');
@@ -251,23 +256,75 @@ class CRM_Contribute_Import_Parser_ContributionTest extends CiviUnitTestCase {
    *
    * @param int $onDuplicateAction
    * @param int|null $expectedResult
-   * @param array|null $mapperSoftCredit
-   * @param array|null $mapperPhoneType
-   * @param array|null $mapperSoftCreditType
+   * @param array|null $mappings
    * @param array|null $fields
    *   Array of field names. Will be calculated from $originalValues if not passed in.
    */
-  protected function runImport(array $originalValues, int $onDuplicateAction, ?int $expectedResult, array $mapperSoftCredit = NULL, array $mapperPhoneType = NULL, array $mapperSoftCreditType = NULL, array $fields = NULL): void {
+  protected function runImport(array $originalValues, int $onDuplicateAction, ?int $expectedResult, array $mappings = [], array $fields = NULL): void {
     if (!$fields) {
       $fields = array_keys($originalValues);
     }
+    $mapper = [];
+    if ($mappings) {
+      foreach ($mappings as $mapping) {
+        $fieldInput = [$mapping['name']];
+        if (!empty($mapping['soft_credit_type_id'])) {
+          $fieldInput[1] = $mapping['soft_credit_match_field'];
+          $fieldInput[2] = $mapping['soft_credit_type_id'];
+        }
+        $mapper[] = $fieldInput;
+      }
+    }
+    else {
+      foreach ($fields as $field) {
+        $mapper[] = [$field];
+      }
+    }
     $values = array_values($originalValues);
-    $parser = new CRM_Contribute_Import_Parser_Contribution($fields, $mapperSoftCredit, $mapperPhoneType, $mapperSoftCreditType);
-    $parser->_contactType = 'Individual';
+    $parser = new CRM_Contribute_Import_Parser_Contribution($fields);
+    $parser->setUserJobID($this->getUserJobID([
+      'onDuplicate' => $onDuplicateAction,
+      'mapper' => $mapper,
+    ]));
     $parser->init();
+
     $this->assertEquals($expectedResult, $parser->import($onDuplicateAction, $values), 'Return code from parser import was not as expected');
   }
 
+  /**
+   * @param array $submittedValues
+   *
+   * @return array
+   *
+   * @throws \API_Exception
+   * @throws \CRM_Core_Exception
+   */
+  protected function getUserJobID(array $submittedValues = []): array {
+    $userJobID = UserJob::create()->setValues([
+      'metadata' => [
+        'submitted_values' => array_merge([
+          'contactType' => CRM_Import_Parser::CONTACT_INDIVIDUAL,
+          'contactSubType' => '',
+          'dataSource' => 'CRM_Import_DataSource_SQL',
+          'sqlQuery' => 'SELECT first_name FROM civicrm_contact',
+          'onDuplicate' => CRM_Import_Parser::DUPLICATE_SKIP,
+          'dedupe_rule_id' => NULL,
+          'dateFormats' => CRM_Core_Form_Date::DATE_yyyy_mm_dd,
+        ], $submittedValues),
+      ],
+      'status_id:name' => 'draft',
+      'type_id:name' => 'contact_import',
+    ])->execute()->first()['id'];
+    if ($submittedValues['dataSource'] ?? NULL === 'CRM_Import_DataSource') {
+      $dataSource = new CRM_Import_DataSource_CSV($userJobID);
+    }
+    else {
+      $dataSource = new CRM_Import_DataSource_SQL($userJobID);
+    }
+    $dataSource->initialize();
+    return $userJobID;
+  }
+
   /**
    * Add a random extra option value
    *