From cb87dffceedc566825d5accaf5e0dd8fbf2547f2 Mon Sep 17 00:00:00 2001 From: minyanyi <109479933+minyanyi@users.noreply.github.com> Date: Mon, 11 May 2026 17:13:53 +0800 Subject: [PATCH] Add optional disabled campaign penalty --- .../Matcher/CreateCampaignContribution.php | 56 +++++++++++++++++++ .../CreateCampaignContribution.suggestion.tpl | 5 ++ .../CreateCampaignContributionTest.php | 56 +++++++++++++++++++ 3 files changed, 117 insertions(+) diff --git a/CRM/Banking/PluginImpl/Matcher/CreateCampaignContribution.php b/CRM/Banking/PluginImpl/Matcher/CreateCampaignContribution.php index dc88f246..008b5ac1 100644 --- a/CRM/Banking/PluginImpl/Matcher/CreateCampaignContribution.php +++ b/CRM/Banking/PluginImpl/Matcher/CreateCampaignContribution.php @@ -78,6 +78,10 @@ public function __construct($config_name) { // ...default for which is: only activities with campaigns $config->activity_with_no_campaign_penalty = 1.00; } + // penalty for disabled campaigns + if (!isset($config->disabled_campaign_penalty)) { + $config->disabled_campaign_penalty = 0.00; + } // default status is 'in Progress' if (!isset($config->active_recurring_contribution_status_ids)) { $config->active_recurring_contribution_status_ids = ['In Progress']; @@ -203,6 +207,7 @@ public function match(CRM_Banking_BAO_BankTransaction $btx, CRM_Banking_Matcher_ $activity_confidence = max($contacts_found[$contact_id], 0.0); $this->logMessage("Found [{$contact_id}] with confidence {$activity_confidence} (including penalty of {$penalty})", 'debug'); $multiple_recurring_contributions_penalty_applied = $this->adjustRatingOfRecurringContributions($contact_id, $activity_confidence, $btx); + $disabled_campaign_penalty_applied = 0.0; // also: add a penalty for activities without campaign (if configured this way) if ($activity_with_no_campaign_penalty > 0.0) { @@ -212,6 +217,12 @@ public function match(CRM_Banking_BAO_BankTransaction $btx, CRM_Banking_Matcher_ $no_campaign_penalty_applied = $activity_with_no_campaign_penalty; } } + if (!empty($activity['campaign_id'])) { + $disabled_campaign_penalty_applied = $this->adjustRatingOfDisabledCampaign( + $activity['campaign_id'], + $activity_confidence + ); + } // apply the general penalty $activity_confidence = min($activity_confidence - $penalty, 1.0); @@ -226,6 +237,7 @@ public function match(CRM_Banking_BAO_BankTransaction $btx, CRM_Banking_Matcher_ $suggestion->setParameter('activity_id', $activity_id); $suggestion->setParameter('multiple_recurring_contributions_penalty_applied', $multiple_recurring_contributions_penalty_applied); $suggestion->setParameter('no_campaign_penalty_applied', $no_campaign_penalty_applied); + $suggestion->setParameter('disabled_campaign_penalty_applied', $disabled_campaign_penalty_applied); $suggestion->setParameter('time_after_activity', strtotime("{$btx->booking_date}") - strtotime($activity['activity_date_time'])); $suggestion->setProbability($activity_confidence); if ($activity_confidence == 1.0) { @@ -367,6 +379,48 @@ public function adjustRatingOfRecurringContributions($contact_id, &$contact_prob } } + /** + * This will reduce the probability of a campaign contribution if + * the referenced campaign is disabled. + * + * @param int|string|null $campaign_id + * Campaign ID. + * @param float $contact_probability + * The probability of the contact to be adjusted. + * + * @return float + * Penalty added to the contact's rating. + */ + public function adjustRatingOfDisabledCampaign($campaign_id, &$contact_probability) { + $disabled_campaign_penalty = (float) ($this->_plugin_config->disabled_campaign_penalty ?? 0); + if ($disabled_campaign_penalty <= 0 || empty($campaign_id)) { + return 0.0; + } + + static $campaign_is_active = []; + $campaign_id = (int) $campaign_id; + + if (!array_key_exists($campaign_id, $campaign_is_active)) { + try { + $campaign = civicrm_api3('Campaign', 'getsingle', ['id' => $campaign_id]); + } + catch (Exception $ex) { + $this->logMessage("Campaign [{$campaign_id}] could not be loaded, disabled-campaign penalty skipped.", 'warn'); + return 0.0; + } + $campaign_is_active[$campaign_id] = !empty($campaign['is_active']); + } + + if ($campaign_is_active[$campaign_id]) { + $this->logMessage("Campaign [{$campaign_id}] is active, no disabled-campaign penalty applied.", 'debug'); + return 0.0; + } + + $contact_probability = max(0.0, $contact_probability - $disabled_campaign_penalty); + $this->logMessage("Campaign [{$campaign_id}] is disabled, suggestion will be reduced by a penalty of {$disabled_campaign_penalty}.", 'info'); + return $disabled_campaign_penalty; + } + /** * If the user has modified the input fields provided by the "visualize" html code, * the new values will be passed here BEFORE execution @@ -391,6 +445,7 @@ public function visualize_match(CRM_Banking_Matcher_Suggestion $match, $btx) { $contact_id = $match->getParameter('contact_id'); $activity_id = $match->getParameter('activity_id'); $no_campaign_penalty_applied = $match->getParameter('no_campaign_penalty_applied'); + $disabled_campaign_penalty_applied = $match->getParameter('disabled_campaign_penalty_applied'); $multiple_recurring_contributions_penalty_applied = $match->getParameter('multiple_recurring_contributions_penalty_applied'); $contribution = $this->get_contribution_data($btx, $match, $contact_id); @@ -440,6 +495,7 @@ public function visualize_match(CRM_Banking_Matcher_Suggestion $match, $btx) { // penalties $smarty_vars['no_campaign_penalty_applied'] = (int) (100.0 * $no_campaign_penalty_applied); + $smarty_vars['disabled_campaign_penalty_applied'] = (int) (100.0 * $disabled_campaign_penalty_applied); $smarty_vars['multiple_recurring_contributions_penalty_applied'] = (int) (100.0 * $multiple_recurring_contributions_penalty_applied); diff --git a/templates/CRM/Banking/PluginImpl/Matcher/CreateCampaignContribution.suggestion.tpl b/templates/CRM/Banking/PluginImpl/Matcher/CreateCampaignContribution.suggestion.tpl index 1612d23f..a88587f7 100644 --- a/templates/CRM/Banking/PluginImpl/Matcher/CreateCampaignContribution.suggestion.tpl +++ b/templates/CRM/Banking/PluginImpl/Matcher/CreateCampaignContribution.suggestion.tpl @@ -36,6 +36,11 @@ {ts 1=$no_campaign_penalty_applied}Caution: the activity found has no campaign, so the score of this suggestion has been reduced by %1%.{/ts}

{/if} + {if $disabled_campaign_penalty_applied} +
+ {ts 1=$disabled_campaign_penalty_applied}Caution: the selected campaign is disabled, so the score of this suggestion has been reduced by %1%.{/ts} +

+ {/if} {ts 1=$activity_link}Based on activity '%1', the following contribution will be created:{/ts}
diff --git a/tests/phpunit/CRM/Banking/Matcher/CreateCampaignContributionTest.php b/tests/phpunit/CRM/Banking/Matcher/CreateCampaignContributionTest.php index 50ff476a..e9a2149f 100644 --- a/tests/phpunit/CRM/Banking/Matcher/CreateCampaignContributionTest.php +++ b/tests/phpunit/CRM/Banking/Matcher/CreateCampaignContributionTest.php @@ -128,4 +128,60 @@ public function testCampaignMatcherBasicNegative():void { ); } + /** + * Disabled campaigns can reduce the suggestion score if configured. + */ + public function testCampaignMatcherAppliesDisabledCampaignPenalty(): void { + $activity_type_id_used_in_matcher_config = 4; + try { + civicrm_api3('OptionValue', 'getsingle', [ + 'option_group_id' => 'activity_type', + 'value' => $activity_type_id_used_in_matcher_config, + ]); + } + catch (Exception $ex) { + $this->fail("This test requires activity type [{$activity_type_id_used_in_matcher_config}] to exist."); + } + + $contact_id = $this->createContact(); + $campaign_id = $this->createCampaign([ + 'is_active' => 0, + ]); + $this->createActivity([ + 'target_id' => $contact_id, + 'activity_status_id' => 'Completed', + 'campaign_id' => $campaign_id, + 'activity_type_id' => $activity_type_id_used_in_matcher_config, + ]); + + $config = json_decode( + file_get_contents($this->getTestResourcePath('matcher/configuration/CampaignMatcher-01.civibanking')), + TRUE, + flags: JSON_THROW_ON_ERROR + ); + $config['config']['campaign_id'] = $campaign_id; + $config['config']['auto_exec'] = 0; + $config['config']['threshold'] = 0.5; + $config['config']['disabled_campaign_penalty'] = 0.25; + $this->configureCiviBankingModuleWithConfig(json_encode($config, JSON_THROW_ON_ERROR)); + + $transaction_id = $this->createTransaction([ + 'purpose' => 'This transaction should create a suggestion with a reduced score', + 'campaign_id' => $campaign_id, + 'contact_id' => $contact_id, + ]); + + $this->runMatchers([$transaction_id]); + + $transaction = $this->getTransactionInstance($transaction_id); + $this->assertNotNull($transaction, 'The test transaction could not be loaded again.'); + + $suggestions = $transaction->getSuggestionList(); + $this->assertCount(1, $suggestions, 'Expected one campaign contribution suggestion.'); + + $suggestion = reset($suggestions); + $this->assertEqualsWithDelta(0.25, (float) $suggestion->getParameter('disabled_campaign_penalty_applied'), 0.00001); + $this->assertEqualsWithDelta(0.75, (float) $suggestion->getProbability(), 0.00001); + } + }