From 91a3d36d58fa73331b1825dcc68678721c086a9f Mon Sep 17 00:00:00 2001 From: Eileen McNaughton Date: Thu, 8 Sep 2022 16:59:16 +1200 Subject: [PATCH] dev/core#1172 Fix Contribution import to handle ContributionSoft in a storable way --- CRM/Contribute/Import/Form/MapField.php | 38 ++-- CRM/Contribute/Import/Parser/Contribution.php | 213 +++++++++++------- CRM/Import/Form/MapField.php | 20 ++ CRM/Import/Forms.php | 2 +- CRM/Import/Parser.php | 20 +- .../Import/Parser/ContributionTest.php | 2 +- .../phpunit/CRMTraits/Import/ParserTrait.php | 3 +- 7 files changed, 188 insertions(+), 110 deletions(-) diff --git a/CRM/Contribute/Import/Form/MapField.php b/CRM/Contribute/Import/Form/MapField.php index 55a8197f8e..543583e334 100644 --- a/CRM/Contribute/Import/Form/MapField.php +++ b/CRM/Contribute/Import/Form/MapField.php @@ -72,7 +72,7 @@ class CRM_Contribute_Import_Form_MapField extends CRM_Import_Form_MapField { * * @throws \CiviCRM_API3_Exception */ - public function buildQuickForm() { + public function buildQuickForm(): void { $this->addSavedMappingFields(); $this->addFormRule([ @@ -80,18 +80,19 @@ class CRM_Contribute_Import_Form_MapField extends CRM_Import_Form_MapField { 'formRule', ], $this); - $sel1 = $this->getAvailableFields(); + $selectColumn1 = $this->getAvailableFields(); - $softCreditFields['contact_id'] = ts('Contact ID'); - $softCreditFields['external_identifier'] = ts('External ID'); - $softCreditFields['email'] = ts('Email'); - - $sel2['soft_credit'] = $softCreditFields; - $sel3['soft_credit']['contact_id'] = $sel3['soft_credit']['external_identifier'] = $sel3['soft_credit']['email'] = CRM_Core_OptionGroup::values('soft_credit_type'); + $selectColumn2 = []; + $softCreditTypes = CRM_Core_OptionGroup::values('soft_credit_type'); + foreach (array_keys($selectColumn1) as $fieldName) { + if (strpos($fieldName, 'soft_credit__contact__') === 0) { + $selectColumn2[$fieldName] = $softCreditTypes; + } + } foreach ($this->getColumnHeaders() as $i => $columnHeader) { $sel = &$this->addElement('hierselect', "mapper[$i]", ts('Mapper for Field %1', [1 => $i]), NULL); - $sel->setOptions([$sel1, $sel2, $sel3]); + $sel->setOptions([$selectColumn1, $selectColumn2]); } $defaults = $this->getDefaults(); $this->setDefaults($defaults); @@ -100,7 +101,7 @@ class CRM_Contribute_Import_Form_MapField extends CRM_Import_Form_MapField { foreach ($defaults as $index => $default) { // e.g swapOptions(document.forms.MapField, 'mapper[0]', 0, 3, 'hs_mapper_0_'); // where 0 is the highest populated field number in the array and 3 is the maximum. - $js .= "swapOptions(document.forms.MapField, '$index', " . (array_key_last(array_filter($default)) ?: 0) . ", 3, 'hs_mapper_0_');\n"; + $js .= "swapOptions(document.forms.MapField, '$index', " . (array_key_last(array_filter($default)) ?: 0) . ", 2, 'hs_mapper_0_');\n"; } $js .= "\n"; $this->assign('initHideBoxes', $js); @@ -113,8 +114,6 @@ class CRM_Contribute_Import_Form_MapField extends CRM_Import_Form_MapField { * * @return array * e.g ['first_name' => 'First Name', 'last_name' => 'Last Name'.... - * - * @throws \API_Exception */ protected function getAvailableFields(): array { $return = []; @@ -129,6 +128,9 @@ class CRM_Contribute_Import_Form_MapField extends CRM_Import_Form_MapField { if ($this->isUpdateExisting() && in_array($name, ['contribution_id', 'invoice_id', 'trxn_id'], TRUE)) { $field['title'] .= (' ' . ts('(match to contribution record)')); } + // Swap out dots for double underscores so as not to break the quick form js. + // We swap this back on postProcess. + $name = str_replace('.', '__', $name); if (($field['entity'] ?? '') === 'Contact' && $this->isFilterContactFields() && empty($field['match_rule'])) { // Filter out metadata that is intended for create & update - this is not available in the quick-form // but is now loaded in the Parser for the LexIM variant. @@ -212,10 +214,14 @@ class CRM_Contribute_Import_Form_MapField extends CRM_Import_Form_MapField { $fieldMapping = $fieldMappings[$i] ?? NULL; if ($fieldMapping) { if ($fieldMapping['name'] !== ts('do_not_import')) { - // $mapping contact_type is not really a contact type - the data has been mangled + // $mapping contact_type is not really a contact type - the 'about this entity' data has been mangled // into that field - see https://lab.civicrm.org/dev/core/-/issues/654 - // Since the soft credit type id is not stored we can't load it here. - $defaults["mapper[$i]"] = [$fieldMapping['name'], $fieldMapping['contact_type'] ?? '', '']; + $softCreditTypeID = ''; + $entityData = json_decode($fieldMapping['contact_type'] ?? '', TRUE); + if (!empty($entityData)) { + $softCreditTypeID = (int) $entityData['soft_credit']['soft_credit_type_id']; + } + $defaults["mapper[$i]"] = [$fieldMapping['name'], $softCreditTypeID]; } } } @@ -241,7 +247,7 @@ class CRM_Contribute_Import_Form_MapField extends CRM_Import_Form_MapField { $ruleFields = $rule['fields']; $weightSum = 0; foreach ($mapper as $mapping) { - if ($mapping[0] === 'external_identifier') { + if ($mapping[0] === 'external_identifier' || $mapping[0] === 'contribution_contact_id' || $mapping[0] === 'contact__id') { // It is enough to have external identifier mapped. $weightSum = $threshold; break; diff --git a/CRM/Contribute/Import/Parser/Contribution.php b/CRM/Contribute/Import/Parser/Contribution.php index 439779c861..46940b7343 100644 --- a/CRM/Contribute/Import/Parser/Contribution.php +++ b/CRM/Contribute/Import/Parser/Contribution.php @@ -199,8 +199,17 @@ class CRM_Contribute_Import_Parser_Contribution extends CRM_Import_Parser { } else { $fieldSpec = $this->getFieldMetadata($mappedField['name']); - $entity = $fieldSpec['entity'] ?? 'Contribution'; - $params[$entity][$this->getFieldMetadata($mappedField['name'])['name']] = $this->getTransformedFieldValue($mappedField['name'], $values[$i]); + $entity = $fieldSpec['entity_instance'] ?? ($fieldSpec['entity'] ?? 'Contribution'); + // If we move this to the parent we can check if the entity config 'supports_multiple' + if ($entity === 'SoftCreditContact') { + $entityKey = json_encode($mappedField['entity_data']); + $entityInstance = $params[$entity][$entityKey] ?? $mappedField['entity_data']['soft_credit']; + $entityInstance['Contact'] = array_merge($entityInstance['Contact'] ?? [], [$this->getFieldMetadata($mappedField['name'])['name'] => $this->getTransformedFieldValue($mappedField['name'], $values[$i])]); + $params[$entity][$entityKey] = $entityInstance; + } + else { + $params[$entity][$this->getFieldMetadata($mappedField['name'])['name']] = $this->getTransformedFieldValue($mappedField['name'], $values[$i]); + } } } return $params; @@ -258,17 +267,28 @@ class CRM_Contribute_Import_Parser_Contribution extends CRM_Import_Parser { $fields = array_merge($fields, $note); $fields = array_merge($fields, CRM_Core_BAO_CustomField::getFieldsForImport('Contribution')); - $fields = array_merge($fields, - [ - 'soft_credit' => [ - 'title' => ts('Soft Credit'), - 'softCredit' => TRUE, - 'headerPattern' => '/Soft Credit/i', - 'options' => FALSE, - 'type' => CRM_Utils_Type::T_STRING, - ], - ] - ); + $fields['soft_credit.contact.id'] = [ + 'title' => ts('Soft Credit Contact ID'), + 'softCredit' => TRUE, + 'name' => 'id', + 'entity' => 'Contact', + 'entity_instance' => 'SoftCreditContact', + 'entity_prefix' => 'soft_credit.contact.', + 'headerPattern' => '/Soft Credit/i', + 'options' => FALSE, + 'type' => CRM_Utils_Type::T_STRING, + 'contact_type' => ['Individual' => 'Individual', 'Household' => 'Household', 'Organization' => 'Organization'], + 'match_rule' => '*', + ]; + foreach ($tmpContactField as $contactField) { + $fields['soft_credit.contact.' . $contactField['name']] = array_merge($contactField, [ + 'title' => ts('Soft Credit Contact') . ' ' . $contactField['title'], + 'softCredit' => TRUE, + 'entity' => 'Contact', + 'entity_instance' => 'SoftCreditContact', + 'entity_prefix' => 'soft_credit.contact.', + ]); + } // add pledge fields only if its is enabled if (CRM_Core_Permission::access('CiviPledge')) { @@ -287,12 +307,6 @@ class CRM_Contribute_Import_Parser_Contribution extends CRM_Import_Parser { $fields = array_merge($fields, $pledgeFields); } - foreach ($fields as $name => $field) { - $fields[$name] = array_merge([ - 'type' => CRM_Utils_Type::T_INT, - 'headerPattern' => '//', - ], $field); - } $this->importableFieldsMetadata = $fields; } } @@ -319,6 +333,8 @@ class CRM_Contribute_Import_Parser_Contribution extends CRM_Import_Parser { 'required_fields_update' => $this->getRequiredFieldsForMatch(), 'required_fields_create' => $this->getRequiredFieldsForCreate(), 'is_base_entity' => TRUE, + 'supports_multiple' => FALSE, + 'is_required' => TRUE, // For now we stick with the action selected on the DataSource page. 'actions' => $this->isUpdateExisting() ? [['id' => 'update', 'text' => ts('Update existing'), 'description' => ts('Skip if no match found')]] : @@ -326,15 +342,18 @@ class CRM_Contribute_Import_Parser_Contribution extends CRM_Import_Parser { 'default_action' => $this->isUpdateExisting() ? 'update' : 'create', 'entity_name' => 'Contribution', 'entity_title' => ts('Contribution'), + 'selected' => ['action' => $this->isUpdateExisting() ? 'update' : 'create'], ], 'Contact' => [ 'text' => ts('Contact Fields'), 'unique_fields' => ['external_identifier', 'id'], 'is_contact' => TRUE, - 'actions' => [ - ['id' => 'select', 'text' => ts('Match existing')], - ['id' => 'update', 'text' => ts('Update existing'), ts('Skip if not found')], - ['id' => 'update_or_create', 'text' => ts('Update or Create')], + 'supports_multiple' => FALSE, + 'actions' => $this->isUpdateExisting() ? $this->getActions(['ignore', 'update']) : $this->getActions(['select', 'update', 'update_or_create']), + 'selected' => [ + 'action' => $this->isUpdateExisting() ? 'ignore' : 'select', + 'contact_type' => $this->getSubmittedValue('contactType'), + 'dedupe_rule' => $this->getDedupeRule($this->getSubmittedValue('contactType'))['name'], ], 'default_action' => 'select', 'entity_name' => 'Contact', @@ -342,18 +361,24 @@ class CRM_Contribute_Import_Parser_Contribution extends CRM_Import_Parser { ], 'SoftCreditContact' => [ 'text' => ts('Soft Credit Contact Fields'), - 'maximum' => count($softCreditTypes), + // It turns out there is actually currently no limit - you can import multiple of the same type. + 'supports_multiple' => TRUE, 'unique_fields' => ['external_identifier', 'id'], 'is_contact' => TRUE, - 'actions' => [ - ['id' => 'select', 'text' => ts('Match existing')], - ['id' => 'update', 'text' => ts('Update existing'), 'description' => ts('Skip if not found')], - ['id' => 'update_or_create', 'text' => ts('Update or Create')], - ], - 'default_action' => 'select', + 'is_required' => FALSE, + 'actions' => array_merge([['id' => 'ignore', 'text' => ts('Do not import')]], $this->getActions(['select', 'update', 'update_or_create'])), + 'selected' => ['contact_type' => '', 'soft_credit_type_id' => '', 'action' => 'ignore'], + 'default_action' => 'ignore', 'entity_name' => 'SoftCreditContact', 'entity_title' => ts('Soft Credit Contact'), - 'entity_data' => ['soft_credit_type_id' => ['required' => TRUE, 'options' => $softCreditTypes]], + 'entity_data' => [ + 'soft_credit_type_id' => [ + 'title' => ts('Soft Credit Type'), + 'is_required' => TRUE, + 'options' => $softCreditTypes, + 'name' => 'soft_credit_type_id', + ], + ], ], ]; } @@ -367,8 +392,13 @@ class CRM_Contribute_Import_Parser_Contribution extends CRM_Import_Parser { public function import(array $values): void { $rowNumber = (int) ($values[array_key_last($values)]); try { - $entityKeyedParams = $this->getMappedRow($values); - $contributionParams = $entityKeyedParams['Contribution']; + $params = $this->getMappedRow($values); + $contributionParams = array_merge(['version' => 3, 'skipRecentView' => TRUE, 'skipCleanMoney' => TRUE], $params['Contribution']); + //CRM-10994 + if (isset($contributionParams['total_amount']) && $contributionParams['total_amount'] == 0) { + $contributionParams['total_amount'] = '0.00'; + } + $existingContribution = $this->lookupContribution($contributionParams); if (empty($existingContribution) && $this->isUpdateExisting()) { throw new CRM_Core_Exception(ts('Matching Contribution record not found. Row was skipped.'), CRM_Import_Parser::ERROR); @@ -377,50 +407,48 @@ class CRM_Contribute_Import_Parser_Contribution extends CRM_Import_Parser { if (empty($contributionParams['id']) && $this->isUpdateExisting()) { throw new CRM_Core_Exception('Empty Contribution and Invoice and Transaction ID. Row was skipped.', CRM_Import_Parser::ERROR); } - $contactID = $contributionParams['contact_id'] ?? ($existingContribution['contact_id'] ?? NULL); - $contactID = $contributionParams['contact_id'] = $this->getContactID($entityKeyedParams['Contact'] ?? [], $contactID); - - // @todo - here we flatten some of the entities back into a single array. - // The entity format is better but the code below needs to be migrated. - $params = $contributionParams; - if (isset($entityKeyedParams['soft_credit'])) { - $params['soft_credit'] = $entityKeyedParams['soft_credit']; - } + $contactID = $contributionParams['contact_id'] = $this->getContactID($params['Contact'] ?? [], $contributionParams['contact_id'] ?? ($existingContribution['contact_id'] ?? NULL), 'Contact'); - $formatted = array_merge(['version' => 3, 'skipRecentView' => TRUE, 'skipCleanMoney' => TRUE, 'contribution_id' => $params['id'] ?? NULL], $params); - //CRM-10994 - if (isset($params['total_amount']) && $params['total_amount'] == 0) { - $params['total_amount'] = '0.00'; + $softCreditParams = []; + foreach ($params['SoftCreditContact'] ?? [] as $index => $softCreditContact) { + $softCreditParams[$index]['soft_credit_type_id'] = $softCreditContact['soft_credit_type_id']; + $softCreditParams[$index]['contact_id'] = $this->getContactID($softCreditContact['Contact'], $softCreditContact['id'] ?? NULL, 'SoftCreditContact'); } - $paramValues = []; - foreach ($params as $key => $field) { - if ($field == NULL || $field === '') { - continue; - } - $paramValues[$key] = $field; - } + $this->deprecatedFormatParams($contributionParams, $contributionParams); - $this->deprecatedFormatParams($paramValues, $formatted); - - if (!empty($params['soft_credit']) && $this->isUpdateExisting()) { + // From this point on we are changing stuff - the prior rows were doing lookups and exiting + // if the lookups failed. + if (isset($params['SoftCreditContact']) && $this->isUpdateExisting()) { //need to check existing soft credit contribution, CRM-3968 $this->deleteExistingSoftCredit($contributionParams['id']); } - $contributionID = civicrm_api3('contribution', 'create', $formatted)['id']; + $contributionID = civicrm_api3('contribution', 'create', $contributionParams)['id']; - if (!empty($entityKeyedParams['Note'])) { - $this->processNote($contributionID, $contactID, $entityKeyedParams['Note']); + if (!empty($softCreditParams)) { + if (empty($contributionParams['total_amount']) || empty($contributionParams['currency'])) { + $contributionParams = Contribution::get()->addSelect('total_amount', 'currency')->addWhere('id', '=', $contributionID)->execute()->first(); + } + foreach ($softCreditParams as $softCreditParam) { + $softCreditParam['contribution_id'] = $contributionID; + $softCreditParam['amount'] = $contributionParams['total_amount']; + $softCreditParam['currency'] = $contributionParams['currency']; + ContributionSoft::create()->setValues($softCreditParam)->execute(); + } + } + if (!empty($params['Note'])) { + $this->processNote($contributionID, $contactID, $params['Note']); } //return soft valid since we need to show how soft credits were added - if (!empty($params['soft_credit'])) { + // because ? historically we did but this seems a bit obsolete. + if (!empty($softCreditParams)) { $this->setImportStatus($rowNumber, $this->getStatus(self::SOFT_CREDIT), '', $contributionID); return; } // process pledge payment assoc w/ the contribution - $this->setImportStatus($rowNumber, $this->processPledgePayments($contributionID, $formatted) ? $this->getStatus(self::PLEDGE_PAYMENT) : $this->getStatus(self::VALID), $contributionID); + $this->setImportStatus($rowNumber, $this->processPledgePayments($contributionID, $contributionParams) ? $this->getStatus(self::PLEDGE_PAYMENT) : $this->getStatus(self::VALID), $contributionID); return; } @@ -536,17 +564,6 @@ class CRM_Contribute_Import_Parser_Contribution extends CRM_Import_Parser { switch ($key) { - case 'soft_credit': - // import contribution record according to select contact type - // validate contact id and external identifier. - foreach ($value as $softKey => $softParam) { - $values['soft_credit'][$softKey] = [ - 'contact_id' => $this->lookupMatchingContact($softParam), - 'soft_credit_type_id' => $softParam['soft_credit_type_id'], - ]; - } - break; - case 'pledge_id': // get total amount of from import fields $totalAmount = $params['total_amount'] ?? NULL; @@ -613,13 +630,12 @@ class CRM_Contribute_Import_Parser_Contribution extends CRM_Import_Parser { 'name' => str_replace('__', '.', $fieldMapping[0]), 'mapping_id' => $mappingID, 'column_number' => $columnNumber, - // The name of the field to match the soft credit on is (crazily) - // stored in 'contact_type' - 'contact_type' => $fieldMapping[1] ?? NULL, - // We also store the field in a sensible key, even if it isn't saved sensibly. - 'soft_credit_match_field' => $fieldMapping[1] ?? NULL, - // This field is actually not saved at all :-( It is lost each time. - 'soft_credit_type_id' => $fieldMapping[2] ?? NULL, + 'soft_credit_type_id' => $fieldMapping[1] ?? NULL, + 'entity_data' => !empty($fieldMapping[1]) ? ['soft_credit' => ['soft_credit_type_id' => $fieldMapping[1]]] : NULL, + // This is the same data as entity_data - it is stored to the database in the contact_type field + // slit your eyes & squint while blinking and you can almost read that as entity_type and not + // hate it. Otherwise go & whinge on https://lab.civicrm.org/dev/core/-/issues/1172 + 'contact_type' => !empty($fieldMapping[1]) ? json_encode(['soft_credit' => ['soft_credit_type_id' => $fieldMapping[1]]]) : NULL, ]; } @@ -711,9 +727,6 @@ class CRM_Contribute_Import_Parser_Contribution extends CRM_Import_Parser { } $title = []; $title[] = $this->getFieldMetadata($mappedField['name'])['title']; - if ($mappedField['soft_credit_match_field']) { - $title[] = $this->getFieldMetadata($mappedField['soft_credit_match_field'])['title']; - } if ($mappedField['soft_credit_type_id']) { $title[] = CRM_Core_PseudoConstant::getLabel('CRM_Contribute_BAO_ContributionSoft', 'soft_credit_type_id', $mappedField['soft_credit_type_id']); } @@ -762,4 +775,40 @@ class CRM_Contribute_Import_Parser_Contribution extends CRM_Import_Parser { Note::save(FALSE)->setRecords([$noteParams])->execute(); } + /** + * Get the actions to display in the rich UI. + * + * Filter by the input actions - e.g ['update' 'select'] will only return those keys. + * + * @param array $actions + * @param string $entity + * + * @return array + */ + protected function getActions(array $actions, $entity = 'Contact'): array { + $actionList['Contact'] = [ + 'ignore' => [ + 'id' => 'ignore', + 'text' => ts('No action'), + 'description' => ts('Contact not altered'), + ], + 'select' => [ + 'id' => 'select', + 'text' => ts('Match existing Contact'), + 'description' => ts('Look up existing contact. Skip row if not found'), + ], + 'update' => [ + 'id' => 'update', + 'text' => ts('Update existing Contact.'), + 'description' => ts('Update existing Contact. Skip row if not found'), + ], + 'update_or_create' => [ + 'id' => 'update_or_create', + 'text' => ts('Update existing Contact or Create'), + 'description' => ts('Create new contact if not found'), + ], + ]; + return array_values(array_intersect_key($actionList[$entity], array_fill_keys($actions, TRUE))); + } + } diff --git a/CRM/Import/Form/MapField.php b/CRM/Import/Form/MapField.php index cac5ea42f0..ababc7c455 100644 --- a/CRM/Import/Form/MapField.php +++ b/CRM/Import/Form/MapField.php @@ -88,6 +88,7 @@ abstract class CRM_Import_Form_MapField extends CRM_Import_Forms { * @noinspection PhpUnhandledExceptionInspection */ public function postProcess() { + $this->updateUserJobMetadata('import_configuration', $this->getImportConfiguration()); $this->updateUserJobMetadata('submitted_values', $this->getSubmittedValues()); $this->saveMapping(); $parser = $this->getParser(); @@ -479,6 +480,9 @@ abstract class CRM_Import_Form_MapField extends CRM_Import_Forms { 'text' => $field['title'], 'id' => $fieldName, 'has_location' => !empty($field['hasLocationType']), + 'default_value' => $field['default_value'] ?? '', + 'contact_type' => $field['contact_type'] ?? NULL, + 'match_rule' => $field['match_rule'] ?? NULL, ]; if (in_array($fieldName, $highlightedFields, TRUE)) { $childField['text'] .= '*'; @@ -520,4 +524,20 @@ abstract class CRM_Import_Form_MapField extends CRM_Import_Forms { return $this->defaultFromHeader($columnHeader, $headerPatterns); } + /** + * Get the import configuration per the user. + * + * When we have wrangled the user's input (e.g to deal with form layer weirdness + * we saved the wrangled version here - leaving the submitted value as submitted. + * + * @return array + */ + protected function getImportConfiguration(): array { + $importConfiguration = []; + foreach ($this->getSubmittedValue('mapper') as $mapperField) { + $importConfiguration[] = $mapperField; + } + return $importConfiguration; + } + } diff --git a/CRM/Import/Forms.php b/CRM/Import/Forms.php index 630ef38e24..3e4130a08f 100644 --- a/CRM/Import/Forms.php +++ b/CRM/Import/Forms.php @@ -711,7 +711,7 @@ class CRM_Import_Forms extends CRM_Core_Form { continue; } // Swap out dots for double underscores so as not to break the quick form js. - // The parser class undoes this when looking up the field. + // We swap this back on postProcess. $name = str_replace('.', '__', $name); $headerPatterns[$name] = $field['headerPattern']; } diff --git a/CRM/Import/Parser.php b/CRM/Import/Parser.php index 08dda9da2a..702bfdcd2b 100644 --- a/CRM/Import/Parser.php +++ b/CRM/Import/Parser.php @@ -517,9 +517,10 @@ abstract class CRM_Import_Parser implements UserJobInterface { } /** - * The form can do it's own work now... + * Do this work on the form layer. * * @deprecated + * * @return array */ public function getHeaderPatterns(): array { @@ -1423,8 +1424,8 @@ abstract class CRM_Import_Parser implements UserJobInterface { * @return string * @throws \API_Exception */ - private function getActionForEntity(string $entity): string { - return $this->getUserJob()['metadata']['entity_metadata'][$entity]['action'] ?? ($this->getImportEntities()[$entity]['default_action'] ?? ''); + protected function getActionForEntity(string $entity): string { + return $this->getUserJob()['metadata']['entity_configuration'][$entity]['action'] ?? ($this->getImportEntities()[$entity]['default_action'] ?? 'select'); } /** @@ -1644,7 +1645,7 @@ abstract class CRM_Import_Parser implements UserJobInterface { $fieldMap = $this->getOddlyMappedMetadataFields(); $fieldMapName = empty($fieldMap[$fieldName]) ? $fieldName : $fieldMap[$fieldName]; - + $fieldMapName = str_replace('__', '.', $fieldMapName); // This whole business of only loading metadata for one type when we actually need it for all is ... dubious. if (empty($this->getImportableFieldsMetadata()[$fieldMapName])) { if ($loadOptions || !$limitToContactType) { @@ -2284,12 +2285,14 @@ abstract class CRM_Import_Parser implements UserJobInterface { * * @param array $contactParams * @param int|null $contactID + * @param string $entity + * Entity, as described in getImportEntities. * - * @return int + * @return int|null * * @throws \CRM_Core_Exception */ - protected function getContactID(array $contactParams, ?int $contactID): int { + protected function getContactID(array $contactParams, ?int $contactID, string $entity): ?int { $contactType = $contactParams['contact_type'] ?? $this->getContactType(); if ($contactID) { $this->validateContactID($contactID, $contactType); @@ -2298,6 +2301,7 @@ abstract class CRM_Import_Parser implements UserJobInterface { $contactID = $this->lookupExternalIdentifier($contactParams['external_identifier'], $contactType, $contactID ?? NULL); } if (!$contactID) { + $action = $this->getActionForEntity($entity); $contactParams['contact_type'] = $contactType; $possibleMatches = $this->getPossibleMatchesByDedupeRule($contactParams); if (count($possibleMatches) === 1) { @@ -2306,8 +2310,8 @@ abstract class CRM_Import_Parser implements UserJobInterface { elseif (count($possibleMatches) > 1) { throw new CRM_Core_Exception(ts('Record duplicates multiple contacts: ') . implode(',', $possibleMatches)); } - else { - throw new CRM_Core_Exception(ts('No matching Contact found')); + elseif (!in_array($action, ['create', 'ignore', 'update_or_create'], TRUE)) { + throw new CRM_Core_Exception(ts('No matching %1 found', [$entity, 'String'])); } } return $contactID; diff --git a/tests/phpunit/CRM/Contribute/Import/Parser/ContributionTest.php b/tests/phpunit/CRM/Contribute/Import/Parser/ContributionTest.php index 6c36e0cad7..f540a47ff5 100644 --- a/tests/phpunit/CRM/Contribute/Import/Parser/ContributionTest.php +++ b/tests/phpunit/CRM/Contribute/Import/Parser/ContributionTest.php @@ -79,7 +79,7 @@ class CRM_Contribute_Import_Parser_ContributionTest extends CiviUnitTestCase { ['name' => 'receive_date'], ['name' => 'financial_type_id'], ['name' => 'external_identifier'], - ['name' => 'soft_credit', 'soft_credit_type_id' => 1, 'soft_credit_match_field' => 'external_identifier'], + ['name' => 'soft_credit.contact.external_identifier', 'soft_credit_type_id' => 1], ]; $this->importCSV('contributions_amount_validate.csv', $mapping, ['onDuplicate' => CRM_Import_Parser::DUPLICATE_SKIP]); diff --git a/tests/phpunit/CRMTraits/Import/ParserTrait.php b/tests/phpunit/CRMTraits/Import/ParserTrait.php index a842e7757d..14ffcc450d 100644 --- a/tests/phpunit/CRMTraits/Import/ParserTrait.php +++ b/tests/phpunit/CRMTraits/Import/ParserTrait.php @@ -79,8 +79,7 @@ trait CRMTraits_Import_ParserTrait { 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']; + $fieldInput[1] = $mapping['soft_credit_type_id']; } $mapper[] = $fieldInput; } -- 2.25.1