diff --git a/.forceignore b/.forceignore
index 02b2d9c2328..860395bcb93 100644
--- a/.forceignore
+++ b/.forceignore
@@ -4,4 +4,7 @@
# LWC Jest
**/__tests__/**
-**/__mocks__/**
\ No newline at end of file
+**/__mocks__/**
+**/tsconfig.json
+
+**/*.ts
diff --git a/.github/ISSUE_TEMPLATE/read-this-for-all-support-and-questions.md b/.github/ISSUE_TEMPLATE/read-this-for-all-support-and-questions.md
new file mode 100644
index 00000000000..86f7cb91d71
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/read-this-for-all-support-and-questions.md
@@ -0,0 +1,15 @@
+---
+name: READ THIS FOR ALL SUPPORT AND QUESTIONS
+about: 'GO HERE: http://sfdc.co/npchub'
+title: ''
+labels: invalid
+assignees: ''
+
+---
+
+==== IMPORTANT NOTES ====
+
+The Nonprofit Success Pack team does not review or respond to support requests or questions posted in this repository.
+
+Instead, please post all questions and issues directly in the Nonprofit Hub of the Trailblazer Community: http://sfdc.co/npchub
+========================================================================
diff --git a/.gitignore b/.gitignore
index 67cc6f63379..56d032e1675 100644
--- a/.gitignore
+++ b/.gitignore
@@ -30,6 +30,7 @@ Referenced Packages
*.sublime-project
*.sublime-workspace
**/.sfdx/
+**/.sf/
**/.vscode/
**/.idea/
**/.mypy_cache/
@@ -42,7 +43,8 @@ robot/Cumulus/results/
datasets/dev_org/test_data.db
*.db
.cci
-
+.sfdx
+.sf
# LWC
force-app/main/default/lwc/.eslintrc.json
/node_modules
diff --git a/CODEOWNERS b/CODEOWNERS
new file mode 100644
index 00000000000..ce7a494556d
--- /dev/null
+++ b/CODEOWNERS
@@ -0,0 +1,2 @@
+# Comment line immediately above ownership line is reserved for related other information. Please be careful while editing.
+#ECCN:Open Source
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 00000000000..b4612a7bc59
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,105 @@
+# Salesforce Open Source Community Code of Conduct
+
+## About the Code of Conduct
+
+Equality is a core value at Salesforce. We believe a diverse and inclusive
+community fosters innovation and creativity, and are committed to building a
+culture where everyone feels included.
+
+Salesforce open-source projects are committed to providing a friendly, safe, and
+welcoming environment for all, regardless of gender identity and expression,
+sexual orientation, disability, physical appearance, body size, ethnicity, nationality,
+race, age, religion, level of experience, education, socioeconomic status, or
+other similar personal characteristics.
+
+The goal of this code of conduct is to specify a baseline standard of behavior so
+that people with different social values and communication styles can work
+together effectively, productively, and respectfully in our open source community.
+It also establishes a mechanism for reporting issues and resolving conflicts.
+
+All questions and reports of abusive, harassing, or otherwise unacceptable behavior
+in a Salesforce open-source project may be reported by contacting the Salesforce
+Open Source Conduct Committee at ossconduct@salesforce.com.
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as
+contributors and maintainers pledge to making participation in our project and
+our community a harassment-free experience for everyone, regardless of gender
+identity and expression, sexual orientation, disability, physical appearance,
+body size, ethnicity, nationality, race, age, religion, level of experience, education,
+socioeconomic status, or other similar personal characteristics.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment
+include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy toward other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or
+advances
+* Personal attacks, insulting/derogatory comments, or trolling
+* Public or private harassment
+* Publishing, or threatening to publish, others' private information—such as
+a physical or electronic address—without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+professional setting
+* Advocating for or encouraging any of the above behaviors
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable
+behavior and are expected to take appropriate and fair corrective action in
+response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or
+reject comments, commits, code, wiki edits, issues, and other contributions
+that are not aligned with this Code of Conduct, or to ban temporarily or
+permanently any contributor for other behaviors that they deem inappropriate,
+threatening, offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces
+when an individual is representing the project or its community. Examples of
+representing a project or community include using an official project email
+address, posting via an official social media account, or acting as an appointed
+representative at an online or offline event. Representation of a project may be
+further defined and clarified by project maintainers.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported by contacting the Salesforce Open Source Conduct Committee
+at ossconduct@salesforce.com. All complaints will be reviewed and investigated
+and will result in a response that is deemed necessary and appropriate to the
+circumstances. The committee is obligated to maintain confidentiality with
+regard to the reporter of an incident. Further details of specific enforcement
+policies may be posted separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good
+faith may face temporary or permanent repercussions as determined by other
+members of the project's leadership and the Salesforce Open Source Conduct
+Committee.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][contributor-covenant-home],
+version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html.
+It includes adaptions and additions from [Go Community Code of Conduct][golang-coc],
+[CNCF Code of Conduct][cncf-coc], and [Microsoft Open Source Code of Conduct][microsoft-coc].
+
+This Code of Conduct is licensed under the [Creative Commons Attribution 3.0 License][cc-by-3-us].
+
+[contributor-covenant-home]: https://www.contributor-covenant.org (https://www.contributor-covenant.org/)
+[golang-coc]: https://golang.org/conduct
+[cncf-coc]: https://github.com/cncf/foundation/blob/master/code-of-conduct.md
+[microsoft-coc]: https://opensource.microsoft.com/codeofconduct/
+[cc-by-3-us]: https://creativecommons.org/licenses/by/3.0/us/
\ No newline at end of file
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 00000000000..a37b5918f02
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,21 @@
+# Contributing Guide For NPSP
+
+This page lists the operational governance model of this project, as well as the recommendations and requirements for how to best contribute to NPSP. We strive to obey these as best as possible. As always, thanks for contributing – we hope these guidelines make it easier and shed some light on our approach and processes.
+
+# Governance Model
+
+## Salesforce Sponsored
+
+The intent and goal of open sourcing this project is to increase the contributor and user base. However, only Salesforce employees will be given `admin` rights and will be the final arbitrars of what contributions are accepted or not.
+
+# Issues, requests & ideas
+
+The Nonprofit Success Pack team does not review or respond to support requests or questions posted in this repository.
+
+Instead, please post all questions and issues directly in the Nonprofit Hub of the Trailblazer Community: http://sfdc.co/npchub
+
+# Code of Conduct
+Please follow our [Code of Conduct](CODE_OF_CONDUCT.md).
+
+# License
+By contributing your code, you agree to license your contribution under the terms of our project [LICENSE](LICENSE) and to sign the [Salesforce CLA](https://cla.salesforce.com/sign-cla)
diff --git a/README.md b/README.md
index 3d1b52e3ca5..cfaf31d2434 100644
--- a/README.md
+++ b/README.md
@@ -1,20 +1,36 @@
![Salesforce Nonprofit Success Pack](https://cloud.githubusercontent.com/assets/450473/18836784/15e1774a-83c7-11e6-8434-0521d4fbebc0.png "Salesforce Nonprofit Success Pack")
-#### For Users
+## Important Note
-* Ask questions or get help
-* Log a confirmed Issue or Feature Request
-* User Documentation
-* Check out existing bugs and feature and enhancement requests.
-* Release Notes and Beta Releases
+In 2023, Salesforce launched the Nonprofit Cloud which is at the foundation of Salesforce for Nonprofits. Nonprofit Cloud helps teams unify their data to work beyond silos and better collaborate, share, learn from, and use their data. Use Nonprofit Cloud as your a single solution to begin or continue in your digital transformation journey. Read more about the [new Nonprofit Cloud](https://www.salesforce.com/blog/new-nonprofit-cloud/). Some key highlights:
+
+* Salesforce will to continue support our managed package products, including the Nonprofit Success Pack. Tens of thousands of customers use our current managed package products to achieve great results.
+* Many of our ISV partners are building fantastic solutions for our managed packages as well as the new Nonprofit Cloud. We’re working with these partners to ensure that they understand our new product architecture, and are able to bring their years of experience and learnings to the new Nonprofit Cloud.
+* Salesforce remains deeply committed to nonprofit pricing. There are many new capabilities within the new Nonprofit Cloud and we work hard to maintain our nonprofit discounts across the new products. Our goals are to simplify access to nonprofit technology and make it easier for customers to get started.
+* Salesforce continues to grant free licenses with the Power of Us Program for the managed packages and Nonprofit Cloud. With Nonprofit Cloud, the Power of Us program also includes more features than ever to accelerate and deepen the nonprofit experience.
+* Take the new product for a test drive. Sign-up for a [Nonprofit Cloud Trial org](https://help.salesforce.com/s/articleView?id=sfdo.NPC_Create_Nonprofit_Cloud_Trial_Org.htm&type=5).
+* Visit the [Nonprofit Hub](https://trailhead.salesforce.com/trailblazer-community/groups/0F9300000001ocxCAA?tab=discussion&sort=LAST_MODIFIED_DATE_DESC) to chat with others about how nonprofits use Salesforce for social good.
+
+---
+### For Nonprofit Success Pack Users and admins
+
+* Check out existing [Nonprofit feature and enhancement requests](https://ideas.salesforce.com/s/search#t=All&sort=relevancy&f:@sfcategoryfull=[Nonprofit%7CNonprofit%20Cloud,Nonprofit%7CNonprofit%20Success%20Pack%20(NPSP)%20-%20Managed%20Package]).
+* [Ask questions or get help with the Nonprofit Success Pack](https://trailhead.salesforce.com/trailblazer-community/groups/0F94S000000kHitSAE)
+* [Ask for support or questions with other Nonprofit Users and Partners](https://trailhead.salesforce.com/trailblazer-community/groups/0F9300000001ocxCAA)
+* [Nonprofit Success Pack (NPSP) Documentation](https://help.salesforce.com/s/articleView?id=sfdo.Nonprofit_Success_Pack.htm)
+* [Release Notes](https://sfdc.co/bnL4Cb)
+* [Known Issues](https://issues.salesforce.com/#f[sfcategoryfull]=Nonprofit%7CNonprofit%20Success%20Pack%20(NPSP)%20-%20Managed%20Package)
+
+### Try out the Nonprofit Success Pack
-#### Try it out
You can install NPSP utilizing our custom application installer into any Developer Edition, Sandbox or Enterprise Edition Salesforce org.
-* NPSP Installer
-#### Meta
+* [NPSP Installer](https://install.salesforce.org/products/npsp)
+
+### Try out the New Nonprofit Cloud
+
+* [Nonprofit Cloud Learning Org Signup](https://help.salesforce.com/s/articleView?id=sfdo.NPC_Create_Nonprofit_Cloud_Trial_Org.htm&type=5)
-The Nonprofit Success Pack (“NPSP”) is an open-source package licensed by Salesforce.org (“SFDO”) under the BSD-3 Clause License, found at https://opensource.org/licenses/BSD-3-Clause. ANY MASTER SUBSCRIPTION AGREEMENT YOU OR YOUR ENTITY MAY HAVE WITH SFDO DOES NOT APPLY TO YOUR USE OF NPSP. NPSP IS PROVIDED “AS IS” AND AS AVAILABLE, AND SFDO MAKES NO WARRANTY OF ANY KIND REGARDING NPSP, WHETHER EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING BUT NOT LIMITED TO ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, FREEDOM FROM DEFECTS OR NON-INFRINGEMENT, TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW.
-SFDO WILL HAVE NO LIABILITY ARISING OUT OF OR RELATED TO YOUR USE OF NPSP FOR ANY DIRECT DAMAGES OR FOR ANY LOST PROFITS, REVENUES, GOODWILL OR INDIRECT, SPECIAL, INCIDENTAL, CONSEQUENTIAL, EXEMPLARY, COVER, BUSINESS INTERRUPTION OR PUNITIVE DAMAGES, WHETHER AN ACTION IS IN CONTRACT OR TORT AND REGARDLESS OF THE THEORY OF LIABILITY, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES OR IF A REMEDY OTHERWISE FAILS OF ITS ESSENTIAL PURPOSE. THE FOREGOING DISCLAIMER WILL NOT APPLY TO THE EXTENT PROHIBITED BY LAW. SFDO DISCLAIMS ALL LIABILITY AND INDEMNIFICATION OBLIGATIONS FOR ANY HARM OR DAMAGES CAUSED BY ANY THIRD-PARTY HOSTING PROVIDERS.
+### Meta
-_ducking-octo-happiness, laughing-archer_
+The Nonprofit Success Pack (“NPSP”) is an open-source package licensed by Salesforce.org (“SFDO”) under the BSD-3 Clause License, found at https://opensource.org/licenses/BSD-3-Clause. ANY MASTER SUBSCRIPTION AGREEMENT YOU OR YOUR ENTITY MAY HAVE WITH SFDO DOES NOT APPLY TO YOUR USE OF NPSP. NPSP IS PROVIDED “AS IS” AND AS AVAILABLE, AND SFDO MAKES NO WARRANTY OF ANY KIND REGARDING NPSP, WHETHER EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING BUT NOT LIMITED TO ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, FREEDOM FROM DEFECTS OR NON-INFRINGEMENT, TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW. SFDO WILL HAVE NO LIABILITY ARISING OUT OF OR RELATED TO YOUR USE OF NPSP FOR ANY DIRECT DAMAGES OR FOR ANY LOST PROFITS, REVENUES, GOODWILL OR INDIRECT, SPECIAL, INCIDENTAL, CONSEQUENTIAL, EXEMPLARY, COVER, BUSINESS INTERRUPTION OR PUNITIVE DAMAGES, WHETHER AN ACTION IS IN CONTRACT OR TORT AND REGARDLESS OF THE THEORY OF LIABILITY, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES OR IF A REMEDY OTHERWISE FAILS OF ITS ESSENTIAL PURPOSE. THE FOREGOING DISCLAIMER WILL NOT APPLY TO THE EXTENT PROHIBITED BY LAW. SFDO DISCLAIMS ALL LIABILITY AND INDEMNIFICATION OBLIGATIONS FOR ANY HARM OR DAMAGES CAUSED BY ANY THIRD-PARTY HOSTING PROVIDERS.
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 00000000000..e31774df287
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,7 @@
+## Security
+
+Please report any security issue to [security@salesforce.com](mailto:security@salesforce.com)
+as soon as it is discovered. This library limits its runtime dependencies in
+order to reduce the total cost of ownership as much as can be, but all consumers
+should remain vigilant and have their security stakeholders review all third-party
+products (3PP) like this one and their dependencies.
\ No newline at end of file
diff --git a/StaticResourceSources/npsp-slds/npsp-common.css b/StaticResourceSources/npsp-slds/npsp-common.css
index 7d781f4f9ec..34e695ff644 100644
--- a/StaticResourceSources/npsp-slds/npsp-common.css
+++ b/StaticResourceSources/npsp-slds/npsp-common.css
@@ -17,6 +17,7 @@ input.lookupInput {
input.lookupInputSLDS {
width: 100%;
margin-right: -30px !important;
+ border-color: #747474 !important;
}
body .dateInput input[type="text"], .slds-vf-scope .dateInput input[type="text"] {
margin-left: 0;
diff --git a/cumulusci.yml b/cumulusci.yml
index 971e3ebc175..2f7b3d2925f 100644
--- a/cumulusci.yml
+++ b/cumulusci.yml
@@ -1,4 +1,4 @@
-minimum_cumulusci_version: 3.23.0
+minimum_cumulusci_version: 3.74.0
project:
name: Cumulus
source_format: sfdx
@@ -221,13 +221,18 @@ tasks:
options:
update_future_releases: True
+ github_release:
+ options:
+ release_content: |
+ Check out the [Salesforce Release Notes](https://sfdc.co/bnL4Cb) or [Known Issues](https://issues.salesforce.com/) for details.
+
github_release_notes:
options:
trial_info: "`TBD`"
is_rd2_enabled:
description: This preflight check ensures that Enhanced Recurring Donations is enabled
- class_path: tasks.is_rd2_enabled
+ class_path: tasks.check_rd2_enablement.is_rd2_enabled
group: NPSP
robot:
@@ -769,6 +774,12 @@ tasks:
- "*.cls"
flows:
+
+ release_production:
+ steps:
+ 3:
+ task: None
+
build_unlocked_test_package:
steps:
0:
diff --git a/force-app/main/adapter/in/sobjects/contact/ContactAdapter.cls b/force-app/main/adapter/in/sobjects/contact/ContactAdapter.cls
index 7309d27d0b7..c39a3216c74 100644
--- a/force-app/main/adapter/in/sobjects/contact/ContactAdapter.cls
+++ b/force-app/main/adapter/in/sobjects/contact/ContactAdapter.cls
@@ -651,7 +651,7 @@ public inherited sharing class ContactAdapter extends fflib_SObjects2 {
// mark the new address as default
// put it on dmlWrapper
//
-
+ // Map to hold the contact-to-address mapping
Map contactAddressesByContact = getContactAddressesByContact(contacts);
// look for duplicates for our proposed new addresses
@@ -665,22 +665,56 @@ public inherited sharing class ContactAdapter extends fflib_SObjects2 {
Address__c newAddressFromContact = contactAddressesByContact.get(contact);
Address__c existingAddressFromContact = existingAddressesByAddress.get(newAddressFromContact);
- // if found a match
- if (contactAddressHasAddressMatch(existingAddressFromContact)) {
+ // Check if the contact's address fields are intentionally set to null
+ Boolean isAddressFieldsNull = String.isBlank(contact.MailingStreet) &&
+ String.isBlank(contact.MailingCity) &&
+ String.isBlank(contact.MailingPostalCode) &&
+ String.isBlank(contact.MailingState) &&
+ String.isBlank(contact.MailingCountry);
+
+ // If all address fields are null, clear the Current_Address__c field and continue
+ if (isAddressFieldsNull) {
+ contact.Current_Address__c = null;
+ continue; // Skip address creation for this contact
+ }
+
+ // Check if the contact has Address Override enabled
+ if (contact.Is_Address_Override__c == true) {
+ // Create a new address record specific to this contact
+ Address__c newAddress = new Address__c();
+ newAddress.Household_Account__c = contact.AccountId; // Link to household/account
+ newAddress.MailingStreet__c = contact.MailingStreet;
+ newAddress.MailingCity__c = contact.MailingCity;
+ newAddress.MailingState__c = contact.MailingState;
+ newAddress.MailingPostalCode__c = contact.MailingPostalCode;
+ newAddress.MailingCountry__c = contact.MailingCountry;
+ newAddress.Default_Address__c = false; // It's not a default address
+
+ // Add the new address to be inserted
+ contactAddressesToInsertByContact.put(contact, newAddress);
+
+ }
+ else if (contactAddressHasAddressMatch(existingAddressFromContact)) {
updateContactAddressFromExistingAddress(contact, existingAddressFromContact);
// Prevent an address that was just inserted by the BeforeInsert trigger from being udpated
// a second time by the AfterInsert trigger.
}
-
- // no match found, and its an override just for this contact
- else if (contact.is_Address_Override__c) {
+ else {
// put it on the list of addresss to create now
contactAddressesToInsertByContact.put(contact, newAddressFromContact);
}
}
-
+ // Insert the new addresses created for contacts with Address Override
insertContactAddresses(contactAddressesToInsertByContact);
+
+ // Update the Current_Address__c field for each contact after the address insertion
+ for (Contact contact : contactAddressesToInsertByContact.keySet()) {
+ Address__c insertedAddress = contactAddressesToInsertByContact.get(contact);
+ if (insertedAddress != null && insertedAddress.Id != null) {
+ contact.Current_Address__c = insertedAddress.Id; // Set the address ID after insertion
+ }
+ }
}
private Set getAddressCreationQueueBeforeUpdate() {
diff --git a/force-app/main/default/aura/RD2_EnablementDelegate/RD2_EnablementDelegate.cmp b/force-app/main/default/aura/RD2_EnablementDelegate/RD2_EnablementDelegate.cmp
index 062a52fbe75..52590e892c7 100644
--- a/force-app/main/default/aura/RD2_EnablementDelegate/RD2_EnablementDelegate.cmp
+++ b/force-app/main/default/aura/RD2_EnablementDelegate/RD2_EnablementDelegate.cmp
@@ -55,8 +55,9 @@
-
-
+
+
+
{!$Label.c.RD2_EnablementDisabledHeader}
@@ -77,7 +78,9 @@
-
{!$Label.c.RD2_EnablementPrepTitle}
+
+
{!$Label.c.RD2_EnablementPrepTitle}
+
diff --git a/force-app/main/default/aura/RD2_EnablementDelegate/RD2_EnablementDelegateController.js b/force-app/main/default/aura/RD2_EnablementDelegate/RD2_EnablementDelegateController.js
index 4853052a48e..06f1524d7e3 100644
--- a/force-app/main/default/aura/RD2_EnablementDelegate/RD2_EnablementDelegateController.js
+++ b/force-app/main/default/aura/RD2_EnablementDelegate/RD2_EnablementDelegateController.js
@@ -28,6 +28,16 @@
helper.handleBatchEvent(component, event, 'v.dryRunBatch');
helper.refreshDryRun(component);
helper.refreshEnable(component);
+ var status = event.Hp.batchProgress.status;
+ var dryRunJob = component.find("dryRunJob");
+ if (["Completed", "Aborted"].includes(status)) {
+ if(dryRunJob){
+ helper.setFocus(component, 'dryRunJob');
+ }
+ else{
+ helper.setFocus(component, 'dryRun2Job');
+ }
+ }
},
handleDryRunError: function (component, event, helper) {
helper.handleBatchError(component, event, 'dryRun');
@@ -44,6 +54,10 @@
handleMigrationStatusChange: function (component, event, helper) {
helper.handleBatchEvent(component, event, 'v.migrationBatch');
helper.refreshMigration(component);
+ var status = event.Hp.batchProgress.status;
+ if (["Completed", "Aborted"].includes(status)) {
+ helper.setFocus(component, 'migrationJob');
+ }
},
handleMigrationError: function (component, event, helper) {
helper.handleBatchError(component, event, 'migration');
diff --git a/force-app/main/default/aura/RD2_EnablementDelegate/RD2_EnablementDelegateHelper.js b/force-app/main/default/aura/RD2_EnablementDelegate/RD2_EnablementDelegateHelper.js
index 45f96989f3c..9a18f91f260 100644
--- a/force-app/main/default/aura/RD2_EnablementDelegate/RD2_EnablementDelegateHelper.js
+++ b/force-app/main/default/aura/RD2_EnablementDelegate/RD2_EnablementDelegateHelper.js
@@ -599,5 +599,22 @@
hideSpinner: function (component, element) {
var spinner = component.find(element);
$A.util.addClass(spinner, 'slds-hide');
- }
+ },
+ /**
+ * @description: Autofocus
+ */
+ setFocus: function (component, elementId) {
+ window.setTimeout(() => {
+ try { var element = component.find(elementId);
+ if (element) {
+ element.getElement().setAttribute('tabindex', '0');
+ element.getElement().focus();
+ element.getElement().setAttribute('tabindex', '-1');
+ }
+ } catch (error) {
+ console.error('Error setting focus on element:', error);
+
+ }
+ }, 0);
+ }
})
diff --git a/force-app/main/default/classes/ACCT_ViewOverride_CTRL.cls b/force-app/main/default/classes/ACCT_ViewOverride_CTRL.cls
index db2605e54b4..fe2f8c32a03 100644
--- a/force-app/main/default/classes/ACCT_ViewOverride_CTRL.cls
+++ b/force-app/main/default/classes/ACCT_ViewOverride_CTRL.cls
@@ -47,7 +47,10 @@ public with sharing class ACCT_ViewOverride_CTRL {
public ACCT_ViewOverride_CTRL (ApexPages.StandardController controller)
{
//get the account with the npe01__one2oneContact__c field for use in the rederict if necessary
- List accounts = [select id, npe01__one2oneContact__c, npe01__SYSTEM_AccountType__c from Account where id = :ApexPages.currentPage().getParameters().get('id')];
+ // WITH SECURITY_ENFORCED isn't really necessary, but it won't hurt anything and should pass automated
+ // security checks
+ List accounts = [select id, npe01__one2oneContact__c, npe01__SYSTEM_AccountType__c
+ from Account where id = :ApexPages.currentPage().getParameters().get('id') WITH SECURITY_ENFORCED];
if (accounts.size() > 0)
{
account = accounts[0];
diff --git a/force-app/main/default/classes/ALLO_Allocations_TDTM.cls b/force-app/main/default/classes/ALLO_Allocations_TDTM.cls
index fa93d4978d1..a0915803a26 100644
--- a/force-app/main/default/classes/ALLO_Allocations_TDTM.cls
+++ b/force-app/main/default/classes/ALLO_Allocations_TDTM.cls
@@ -226,14 +226,22 @@ public class ALLO_Allocations_TDTM extends TDTM_Runnable {
private void runPaymentTriggerHandler(List newlist, List oldlist,
TDTM_Runnable.Action triggerAction, Schema.DescribeSObjectResult objResult) {
+
+ //do not run if Payment allocations are disabled
+ if (isPaymentAllocationsEnabled()) {
+ processPaymentAllocations(newlist, oldlist, triggerAction);
+ }
+
+ if (triggerAction == TDTM_Runnable.Action.AfterInsert || triggerAction == TDTM_Runnable.Action.AfterUpdate) {
+ handleAllocationsAndOpportunities(newList, oldList);
+ }
+ }
+
+ private void processPaymentAllocations(List newlist, List oldlist, TDTM_Runnable.Action triggerAction) {
List listPmtsForProcessing = new List();
List pmtsWithNullOldAmount = new List();
List pmtsNeedingAllocations = new List();
- //do not run if Payment allocations are disabled
- if (!isPaymentAllocationsEnabled()) {
- return;
- }
for (integer i=0; i newList,
+ List oldList
+ ) {
+ List refunds = new List();
+ List oldRefunds = new List();
+ for (Integer i = 0; i < newList.size(); i++) {
+ if (newList[i].npe01__Payment_Amount__c < 0 && String.isBlank(newList[i].Elevate_Payment_ID__c) && hasRefundDebitType(newList[i])) {
+ refunds.add(newList[i]);
+ if ( oldList != null) {
+ oldRefunds.add(oldList[i]);
+ }
+ }
+ }
+
+ if (refunds.isEmpty()) {
+ return;
+ }
+
+ PMT_RefundService refundService = new PMT_RefundService()
+ .withRefundRecords(refunds)
+ .withOldRefundRecords(oldRefunds);
+
+ refundService.adjustAllocationsAndOpportunities()
+ .updateAllocationsAndOpportunities();
+
+ List errorRecords = refundService.getErrors();
+
+ for (ErrorRecord error : errorRecords) {
+ if (error.hasError()) {
+ error.getRecord().addError(error.getFirstError());
+ }
+ }
+ }
+
+ private Boolean hasRefundDebitType (npe01__OppPayment__c payment) {
+ return (payment.DebitType__c == PMT_RefundService.PARTIAL_REFUND
+ || payment.DebitType__c == PMT_RefundService.FULL_REFUND);
+ }
+
/*******************************************************************************************************
* @description Allocations before trigger handler on GAU Allocation. Validates allocation data per
* object and per parent object to avoid badly created allocations, exceeding opportunity amount,
@@ -747,8 +794,18 @@ public class ALLO_Allocations_TDTM extends TDTM_Runnable {
} else {
for (Allocation__c allo : oppWrap.listAllo) {
+ // if the Opportunity is for $0, we can safely update fixed Allocations to be $0
+ // This fixes a bug with Elevate refunds when non-default Allocations have fixed (not percent based) Amounts
+ if (oppWrap.parentAmount == 0 && allo.Percent__c == null) {
+ oppWrap.totalAmount -= allo.Amount__c;
+ allo.Amount__c = 0;
+
+ if (UserInfo.isMultiCurrencyOrganization()) {
+ allo.put('CurrencyIsoCode', opp.get('CurrencyIsoCode'));
+ }
+ dmlWrapper.objectsToUpdate.add(allo);
//if the percentage changed, recalculate the amount
- if (allo.Percent__c!=null && allo.Percent__c>0 && allo.Amount__c != (oppWrap.parentAmount * allo.Percent__c * .01).setScale(2)) {
+ } else if (allo.Percent__c != null && allo.Percent__c > 0 && allo.Amount__c != (oppWrap.parentAmount * allo.Percent__c * .01).setScale(2)) {
//remove the previous amount, recalculate the amount, and add it back
oppWrap.totalAmount -= allo.Amount__c;
allo.Amount__c = (oppWrap.parentAmount * allo.Percent__c * .01).setScale(2);
@@ -789,9 +846,10 @@ public class ALLO_Allocations_TDTM extends TDTM_Runnable {
}
}
//if the Opportunity amount has decreased, we run the risk of allocations exceeding the total opportunity amount
- if (oppWrap.totalAmount > oppWrap.parentAmount)
+ if (oppWrap.totalAmount > oppWrap.parentAmount) {
//using addError here because we want to block opportunity update, display the error inline, and block the DML of updating all the related allocations
opp.Amount.addError(Label.alloExceedsOppAmount);
+ }
}
//if we have no allocations for this opportunity, defaults are enabled, and the opportunity has an amount, make a default allocation
} else if (settings.Default_Allocations_Enabled__c && opp.Amount != null) {
diff --git a/force-app/main/default/classes/ALLO_Allocations_TEST.cls b/force-app/main/default/classes/ALLO_Allocations_TEST.cls
index c85f072d966..f8bd1db8208 100644
--- a/force-app/main/default/classes/ALLO_Allocations_TEST.cls
+++ b/force-app/main/default/classes/ALLO_Allocations_TEST.cls
@@ -433,6 +433,104 @@ private with sharing class ALLO_Allocations_TEST {
}
+ /*******************************************************************************************************
+ * @description When working with negative and positive Payments:
+ * A mixture of positive and negative payments should be successfully inserted
+ * This test was created to ensure we are not breaking Accounting Subledger functionality
+ ********************************************************************************************************/
+ static testMethod void pmtsWithPositiveAndNegativeAmounts() {
+ Date todaysDate = System.today();
+ Date tomorrowsDate = System.today().addDays(1);
+ Date todayPlusOneMonthsDate = System.today().addMonths(1);
+ Date todayPlusTwoMonthsDate = System.today().addMonths(2);
+ Date todayPlusThreeMonthsDate = System.today().addMonths(3);
+
+ General_Accounting_Unit__c defaultGau = new General_Accounting_Unit__c(Name='default GAU');
+ insert defaultGau;
+
+ setupSettings(new Allocations_Settings__c(
+ Payment_Allocations_Enabled__c = true,
+ Default_Allocations_Enabled__c = true,
+ Default__c = defaultGau.Id));
+
+ Account acc = new Account(Name='Account-pmtAmountChange');
+ insert acc;
+
+ List gaus = UTIL_UnitTestData_TEST.createGAUs(4);
+ insert gaus;
+
+ Opportunity opp = new Opportunity(Name='Opp-pmtAmountChange', Amount = 1000, AccountID=acc.Id, CloseDate=System.today(), StageName=UTIL_UnitTestData_TEST.getOpenStage(), npe01__Do_Not_Automatically_Create_Payment__c=true);
+ insert opp;
+
+ //Create Opportunity Allocation Defaults
+ List oppAllocations = new List();
+ Allocation__c opp1Allo1 = new Allocation__c(General_Accounting_Unit__c = gaus[0].Id,
+ Amount__c = 200, //20%
+ Opportunity__c = opp.Id);
+ Allocation__c opp1Allo2 = new Allocation__c(General_Accounting_Unit__c = gaus[1].Id,
+ Amount__c = 800, //80%
+ Opportunity__c = opp.Id);
+
+ oppAllocations.add(opp1Allo1);
+ oppAllocations.add(opp1Allo2);
+
+ insert oppAllocations;
+
+ List payments = new List();
+ npe01__OppPayment__c p1 = new npe01__OppPayment__c(npe01__Opportunity__c = opp.Id,
+ npe01__Scheduled_Date__c = todaysDate, npe01__Payment_Amount__c = 400);
+ npe01__OppPayment__c p2 = new npe01__OppPayment__c(npe01__Opportunity__c = opp.Id,
+ npe01__Scheduled_Date__c = todayPlusOneMonthsDate,
+ npe01__Payment_Amount__c = 600);
+ payments.add(p1);
+ payments.add(p2);
+
+ insert payments;
+
+ opp.StageName = UTIL_UnitTestData_TEST.getClosedWonStage();
+ update opp;
+
+ payments = new List();
+
+ //Now Insert a mixture of payments
+ p1 = new npe01__OppPayment__c(
+ npe01__Opportunity__c = opp.Id,
+ npe01__Payment_Date__c = todaysDate,
+ npe01__Paid__c = true,
+ npe01__Payment_Amount__c = 300);
+ p2 = new npe01__OppPayment__c(
+ npe01__Opportunity__c = opp.Id,
+ npe01__Payment_Date__c = todayPlusOneMonthsDate,
+ npe01__Paid__c = true,
+ npe01__Payment_Amount__c = -200);
+ npe01__OppPayment__c p3 = new npe01__OppPayment__c(
+ npe01__Opportunity__c = opp.Id,
+ npe01__Payment_Date__c = todayPlusTwoMonthsDate,
+ npe01__Paid__c = true,
+ npe01__Payment_Amount__c = 500);
+ npe01__OppPayment__c p4 = new npe01__OppPayment__c(
+ npe01__Opportunity__c = opp.Id,
+ npe01__Payment_Date__c = todayPlusThreeMonthsDate,
+ npe01__Paid__c = true,
+ npe01__Payment_Amount__c = -600);
+
+ payments.add(p1);
+ payments.add(p2);
+ payments.add(p3);
+ payments.add(p4);
+
+ Test.startTest();
+ insert payments;
+ Test.stopTest();
+
+ List queryAllo = getAllocationsOrderByPercent(p1.Id, gaus[0].Id);
+ System.assertEquals(300 * 0.2, queryAllo[0].Amount__c, 'The allocation amount should be 20% of the Payment');
+
+ queryAllo = getAllocationsOrderByPercent(p2.Id, gaus[0].Id);
+ System.assertEquals(-200 * 0.2, queryAllo[0].Amount__c, 'The allocation amount should be 20% of the Payment');
+
+ }
+
/*******************************************************************************************************
* @description When working with negative amount Payments:
* Updating the amount will adjust the percentage allocations.
@@ -1393,7 +1491,7 @@ private with sharing class ALLO_Allocations_TEST {
createOppAllocation(gau1.Id, opps[0].Id, null, 50)
);
- // Fix Amount Allocation for $0 to GAU 2
+ // Fix Amount Allocation for $10 to GAU 2
newAllos.add(
createOppAllocation(gau2.Id, opps[0].Id, 10, null)
);
@@ -1402,7 +1500,62 @@ private with sharing class ALLO_Allocations_TEST {
insert newAllos;
System.assert(false, 'Expected an exception due to Opportunity being Overallocated.');
} catch (Exception e) {
- System.assert(e.getMessage().contains(Label.alloTotalExceedsOppAmt), 'Expected Exception Text: ' + Label.alloExceedsOppAmount + '; Actual: ' + e.getMessage());
+ System.assert(e.getMessage().contains(Label.alloTotalExceedsOppAmt), 'Expected Exception Text: ' + Label.alloTotalExceedsOppAmt + '; Actual: ' + e.getMessage());
+ }
+
+ Test.stopTest();
+ }
+
+ /*******************************************************************************************************
+ * @description Tests that no error is generated if Opportunity Amount becomes 0 and a non zero fixed
+ * Amount Allocation exists
+ ********************************************************************************************************/
+ @isTest
+ private static void testFixupForNonZeroFixedAmountAllocationIfOpportunityAmountIsZero() {
+ List gaus = new List();
+
+ General_Accounting_Unit__c defaultGau = new General_Accounting_Unit__c(Name='General');
+ gaus.add(defaultGAU);
+ General_Accounting_Unit__c gau1 = new General_Accounting_Unit__c(Name = 'GAU 1');
+ gaus.add(gau1);
+
+ insert gaus;
+
+ setupSettings(new Allocations_Settings__c(Default_Allocations_Enabled__c = true, Default__c = defaultGau.Id));
+
+ List accs = UTIL_UnitTestData_TEST.createMultipleTestAccounts(1, null);
+
+ insert accs;
+
+ List opps = UTIL_UnitTestData_TEST.oppsForAccountList(accs, null, UTIL_UnitTestData_TEST.getClosedWonStage(), System.today(), 10, null, null);
+
+ insert opps;
+ Opportunity opportunity = opps[0];
+
+ Test.startTest();
+
+ List newAllos = new List();
+
+ // Fix Amount Allocation for $10 to GAU 1
+ newAllos.add(
+ createOppAllocation(gau1.Id, opportunity.Id, 10, null)
+ );
+
+ insert newAllos;
+
+ try {
+ opportunity.Amount = 5;
+ update opportunity;
+ System.assert(false, 'Expected an exception due to Opportunity being Overallocated.');
+ } catch (Exception e) {
+ System.assert(e.getMessage().contains(Label.alloExceedsOppAmount), 'Expected Exception Text: ' + Label.alloExceedsOppAmount + '; Actual: ' + e.getMessage());
+ }
+
+ opportunity.Amount = 0;
+ update opportunity;
+ Map oppAllos = getOpportunityAllocationsByGAU(opportunity.Id);
+ for(Id gauId : oppAllos.keySet()) {
+ System.assertEquals(0, oppAllos.get(gauId).Amount__c, 'Allocaiton Amount should be 0');
}
Test.stopTest();
diff --git a/force-app/main/default/classes/ALLO_ManageAllocations_CTRL.cls b/force-app/main/default/classes/ALLO_ManageAllocations_CTRL.cls
index faa32afe944..d8d41d0bb2c 100644
--- a/force-app/main/default/classes/ALLO_ManageAllocations_CTRL.cls
+++ b/force-app/main/default/classes/ALLO_ManageAllocations_CTRL.cls
@@ -52,6 +52,69 @@ public with sharing class ALLO_ManageAllocations_CTRL {
set;
}
+ private Boolean canCreate {
+ get {
+ if (this.canCreate == null) {
+ this.canCreate = this.checkCreate();
+ }
+
+ return this.canCreate;
+ }
+ set;
+ }
+
+ private Boolean canDelete {
+ get {
+ if (this.canDelete == null) {
+ this.canDelete = this.checkDelete();
+ }
+
+ return this.canDelete;
+ }
+ set;
+ }
+
+ private Boolean canUpdate {
+ get {
+ if (this.canUpdate == null) {
+ this.canUpdate = this.checkUpdate();
+ }
+
+ return this.canUpdate;
+ }
+ set;
+ }
+
+ private Set getFieldsForFLSCheck() {
+ Set objectFields = new Set();
+
+ objectFields.add(Allocation__c.Amount__c.getDescribe().getSobjectField());
+ objectFields.add(Allocation__c.Percent__c.getDescribe().getSobjectField());
+ objectFields.add(Allocation__c.General_Accounting_Unit__c.getDescribe().getSobjectField());
+
+ for(Schema.FieldSetMember additionalField : additionalAllocationFields) {
+ Schema.DescribeFieldResult fieldResult = additionalField.getSObjectField().getDescribe();
+ if (fieldResult.isCalculated() || !fieldResult.permissionable || additionalField.getFieldPath().contains('__r')) {
+ continue;
+ }
+ objectFields.add(additionalField.getSObjectField());
+ }
+
+ return objectFields;
+ }
+
+ private Boolean checkCreate() {
+ return UTIL_Permissions.getInstance().canCreate(Allocation__c.SObjectType, fieldsForFLSCheck);
+ }
+
+ private Boolean checkDelete() {
+ return UTIL_Permissions.getInstance().canDelete(Allocation__c.SObjectType);
+ }
+
+ private Boolean checkUpdate() {
+ return UTIL_Permissions.getInstance().canUpdate(Allocation__c.SObjectType, fieldsForFLSCheck);
+ }
+
public String getNamespace() {
return UTIL_Namespace.getComponentNamespace();
}
@@ -69,6 +132,16 @@ public with sharing class ALLO_ManageAllocations_CTRL {
set;
}
+ private Set fieldsForFLSCheck {
+ get {
+ if (fieldsForFLSCheck == null) {
+ fieldsForFLSCheck = getFieldsForFLSCheck();
+ }
+ return fieldsForFLSCheck;
+ }
+ set;
+ }
+
/** @description List of allocations to delete when the user clicks Save.*/
public list allocationsToBeDeleted = new list();
/** @description The id of the parent object; Opportunity, Campaign, or Recurring Donation.*/
@@ -295,6 +368,9 @@ public with sharing class ALLO_ManageAllocations_CTRL {
Savepoint sp = Database.setSavepoint();
try {
if (!allocationsToBeDeleted.isEmpty()) {
+ if (!canDelete) {
+ UTIL_AuraEnabledCommon.throwAuraHandledException(System.Label.commonAccessErrorMessage);
+ }
TDTM_ProcessControl.setRecursionFlag(TDTM_ProcessControl.flag.ALLOC, false);
delete allocationsToBeDeleted;
@@ -309,11 +385,17 @@ public with sharing class ALLO_ManageAllocations_CTRL {
}
if (!listAlloForUpdate.isEmpty()) {
+ if (!canUpdate) {
+ UTIL_AuraEnabledCommon.throwAuraHandledException(System.Label.commonAccessErrorMessage);
+ }
TDTM_ProcessControl.setRecursionFlag(TDTM_ProcessControl.flag.ALLOC, false);
update listAlloForUpdate;
}
if (!listAlloForInsert.isEmpty()) {
+ if (!canCreate) {
+ UTIL_AuraEnabledCommon.throwAuraHandledException(System.Label.commonAccessErrorMessage);
+ }
TDTM_ProcessControl.setRecursionFlag(TDTM_ProcessControl.flag.ALLOC, false);
insert listAlloForInsert;
}
@@ -354,7 +436,7 @@ public with sharing class ALLO_ManageAllocations_CTRL {
new ApexPages.Message(
ApexPages.Severity.WARNING,
String.format(
- System.Label.exceptionDeletePermission,
+ System.Label.commonAccessErrorMessage,
new String[]{UTIL_Describe.getObjectLabel(UTIL_Namespace.StrTokenNSPrefix('Allocation__c'))})));
}
diff --git a/force-app/main/default/classes/AllocationSelector.cls b/force-app/main/default/classes/AllocationSelector.cls
new file mode 100644
index 00000000000..ff6d89c2021
--- /dev/null
+++ b/force-app/main/default/classes/AllocationSelector.cls
@@ -0,0 +1,56 @@
+/*
+ Copyright (c) 2022, Salesforce.org
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of Salesforce.org nor the names of
+ its contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE.
+*/
+/**
+* @author Salesforce.org
+* @date 2022
+* @group Allocation
+* @description Selector Class for Allocation__c Sobject
+*/
+public with sharing class AllocationSelector {
+ public List getOpportunityAllocations(Set oppIds) {
+ String soql = new UTIL_Query()
+ .withFrom(Allocation__c.SObjectType)
+ .withSelectFields(getStandardAllocationFields())
+ .withWhere('Opportunity__c IN: oppIds')
+ .build();
+
+ return Database.query(soql);
+ }
+
+ private Set getStandardAllocationFields() {
+ return new Set {
+ String.valueOf(Allocation__c.Percent__c),
+ String.valueOf(Allocation__c.Amount__c),
+ String.valueOf(Allocation__c.Opportunity__c),
+ UTIL_Namespace.StrTokenNSPrefix('Allocation__c.Opportunity__r.Amount'),
+ String.valueOf(Allocation__c.General_Accounting_Unit__c)
+ };
+ }
+}
diff --git a/force-app/main/default/classes/AllocationSelector.cls-meta.xml b/force-app/main/default/classes/AllocationSelector.cls-meta.xml
new file mode 100644
index 00000000000..4b0bc9f3879
--- /dev/null
+++ b/force-app/main/default/classes/AllocationSelector.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 55.0
+ Active
+
diff --git a/force-app/main/default/classes/AllocationSelector_TEST.cls b/force-app/main/default/classes/AllocationSelector_TEST.cls
new file mode 100644
index 00000000000..eaeb8ac27c5
--- /dev/null
+++ b/force-app/main/default/classes/AllocationSelector_TEST.cls
@@ -0,0 +1,60 @@
+/*
+ Copyright (c) 2022, Salesforce.org
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of Salesforce.org nor the names of
+ its contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE.
+*/
+/**
+* @author Salesforce.org
+* @date 2022
+* @group Opportunity
+* @description Test and Mock class for OpportunitySelector
+*/
+@isTest
+public with sharing class AllocationSelector_TEST {
+
+ public class Stub implements System.StubProvider {
+ public List allocationRecords;
+
+ public Object handleMethodCall(
+ Object stubbedObject,
+ String methodName,
+ Type returnType,
+ List paramTypes,
+ List paramNames,
+ List