From bf0ec2a0cfc4e023bbab5ee20d20fdb350d8181b Mon Sep 17 00:00:00 2001 From: Allison Letts Date: Tue, 30 Mar 2021 12:57:46 -0400 Subject: [PATCH 1/2] resolving membership report type (accounts with memberships) --- ...ounts_with_Memberships.reportType-meta.xml | 48 +- .../reports/Memberships.reportFolder-meta.xml | 5 - src/applications/Membership.app | 21 + src/classes/ConfigurationService.cls | 17 + src/classes/ConfigurationService.cls-meta.xml | 5 + src/classes/ContactHandler.cls | 37 + src/classes/ContactHandler.cls-meta.xml | 5 + src/classes/MembershipContactRoleService.cls | 88 +++ .../MembershipContactRoleService.cls-meta.xml | 5 + .../MembershipContactRoleService_TEST.cls | 163 +++++ ...ershipContactRoleService_TEST.cls-meta.xml | 5 + src/classes/MembershipHandler.cls | 126 ++++ src/classes/MembershipHandler.cls-meta.xml | 5 + src/classes/MembershipHandler_TEST.cls | 135 ++++ .../MembershipHandler_TEST.cls-meta.xml | 5 + src/classes/TriggerHandler.cls | 240 +++++++ src/classes/TriggerHandler.cls-meta.xml | 5 + src/classes/TriggerHandler_Test.cls | 281 ++++++++ src/classes/TriggerHandler_Test.cls-meta.xml | 5 + .../Membership_UtilityBar.flexipage | 16 + .../Membership_Status.globalValueSet | 25 + .../Membership_Type.globalValueSet | 20 + ...e__c-Membership Contact Role Layout.layout | 108 +++ .../Membership__c-Membership Layout.layout | 248 +++++++ ...LineItem-Opportunity Product Layout.layout | 98 +++ src/objects/Account.object | 102 +++ src/objects/Contact.object | 104 +++ src/objects/Membership_Contact_Role__c.object | 599 ++++++++++++++++ src/objects/Membership__c.object | 650 ++++++++++++++++++ src/objects/OpportunityLineItem.object | 26 + src/package.xml | 111 +++ .../Manage_Memberships.permissionset | 96 +++ ...count.New_Corporate_Membership.quickAction | 37 + .../Account.New_Membership.quickAction | 33 + .../Contact.New_Membership.quickAction | 41 ++ ..._c.New_Membership_Contact_Role.quickAction | 47 ++ .../Opportunity.New_Membership.quickAction | 49 ++ .../Accounts_with_Memberships.reportType | 346 ++++++++++ src/reports/Memberships-meta.xml | 4 + ...s_w_Memberships_but_no_Contacts_ZBx.report | 84 +++ src/tabs/Membership__c.tab | 6 + src/triggers/ContactTrigger.trigger | 7 + src/triggers/ContactTrigger.trigger-meta.xml | 5 + src/triggers/MembershipTrigger.trigger | 8 + .../MembershipTrigger.trigger-meta.xml | 5 + 45 files changed, 4026 insertions(+), 50 deletions(-) create mode 100644 src/applications/Membership.app create mode 100644 src/classes/ConfigurationService.cls create mode 100644 src/classes/ConfigurationService.cls-meta.xml create mode 100644 src/classes/ContactHandler.cls create mode 100644 src/classes/ContactHandler.cls-meta.xml create mode 100644 src/classes/MembershipContactRoleService.cls create mode 100644 src/classes/MembershipContactRoleService.cls-meta.xml create mode 100644 src/classes/MembershipContactRoleService_TEST.cls create mode 100644 src/classes/MembershipContactRoleService_TEST.cls-meta.xml create mode 100644 src/classes/MembershipHandler.cls create mode 100644 src/classes/MembershipHandler.cls-meta.xml create mode 100644 src/classes/MembershipHandler_TEST.cls create mode 100644 src/classes/MembershipHandler_TEST.cls-meta.xml create mode 100644 src/classes/TriggerHandler.cls create mode 100644 src/classes/TriggerHandler.cls-meta.xml create mode 100644 src/classes/TriggerHandler_Test.cls create mode 100644 src/classes/TriggerHandler_Test.cls-meta.xml create mode 100644 src/flexipages/Membership_UtilityBar.flexipage create mode 100644 src/globalValueSets/Membership_Status.globalValueSet create mode 100644 src/globalValueSets/Membership_Type.globalValueSet create mode 100644 src/layouts/Membership_Contact_Role__c-Membership Contact Role Layout.layout create mode 100644 src/layouts/Membership__c-Membership Layout.layout create mode 100644 src/layouts/OpportunityLineItem-Opportunity Product Layout.layout create mode 100644 src/objects/Account.object create mode 100644 src/objects/Contact.object create mode 100644 src/objects/Membership_Contact_Role__c.object create mode 100644 src/objects/Membership__c.object create mode 100644 src/objects/OpportunityLineItem.object create mode 100644 src/package.xml create mode 100644 src/permissionsets/Manage_Memberships.permissionset create mode 100644 src/quickActions/Account.New_Corporate_Membership.quickAction create mode 100644 src/quickActions/Account.New_Membership.quickAction create mode 100644 src/quickActions/Contact.New_Membership.quickAction create mode 100644 src/quickActions/Membership__c.New_Membership_Contact_Role.quickAction create mode 100644 src/quickActions/Opportunity.New_Membership.quickAction create mode 100644 src/reportTypes/Accounts_with_Memberships.reportType create mode 100644 src/reports/Memberships-meta.xml create mode 100644 src/reports/Memberships/Accounts_w_Memberships_but_no_Contacts_ZBx.report create mode 100644 src/tabs/Membership__c.tab create mode 100644 src/triggers/ContactTrigger.trigger create mode 100644 src/triggers/ContactTrigger.trigger-meta.xml create mode 100644 src/triggers/MembershipTrigger.trigger create mode 100644 src/triggers/MembershipTrigger.trigger-meta.xml diff --git a/force-app/main/default/reportTypes/Accounts_with_Memberships.reportType-meta.xml b/force-app/main/default/reportTypes/Accounts_with_Memberships.reportType-meta.xml index 9a05b848..6fc7ad47 100644 --- a/force-app/main/default/reportTypes/Accounts_with_Memberships.reportType-meta.xml +++ b/force-app/main/default/reportTypes/Accounts_with_Memberships.reportType-meta.xml @@ -220,56 +220,14 @@ Jigsaw Account
- - false - CleanStatus - Account
-
false AccountSource Account
- - false - DunsNumber - Account
-
- - false - Tradestyle - Account
-
- - false - NaicsCode - Account
-
- - false - NaicsDesc - Account
-
- - false - YearStarted - Account
-
- - false - SicDesc - Account
-
- - false - DandbCompany - Account
-
- - false - OperatingHours - Account
-
+ + + false First_Membership_Start_Date__c diff --git a/force-app/main/default/reports/Memberships.reportFolder-meta.xml b/force-app/main/default/reports/Memberships.reportFolder-meta.xml index 72c46d33..ec95120f 100644 --- a/force-app/main/default/reports/Memberships.reportFolder-meta.xml +++ b/force-app/main/default/reports/Memberships.reportFolder-meta.xml @@ -1,9 +1,4 @@ - - Manage - test-wqsxxgpoyuih@example.com - User - Memberships diff --git a/src/applications/Membership.app b/src/applications/Membership.app new file mode 100644 index 00000000..dd08eac0 --- /dev/null +++ b/src/applications/Membership.app @@ -0,0 +1,21 @@ + + + + #0070D2 + false + + Small + Large + false + false + + Standard + standard-home + standard-Account + standard-Contact + Membership__c + standard-report + standard-Dashboard + Lightning + Membership_UtilityBar + diff --git a/src/classes/ConfigurationService.cls b/src/classes/ConfigurationService.cls new file mode 100644 index 00000000..db3d9e03 --- /dev/null +++ b/src/classes/ConfigurationService.cls @@ -0,0 +1,17 @@ +public without sharing class ConfigurationService { + public List getIndividualMembershipTypes() { + return new List{'Individual'}; + } + + public List getHouseholdMembershipTypes() { + return new List{'Household'}; + } + + public List getCorporateMembershipTypes() { + return new List{'Corporate'}; + } + + public List getNonIndividualMembershipTypes() { + return new List{'Household', 'Corporate'}; + } +} diff --git a/src/classes/ConfigurationService.cls-meta.xml b/src/classes/ConfigurationService.cls-meta.xml new file mode 100644 index 00000000..8e4d11f8 --- /dev/null +++ b/src/classes/ConfigurationService.cls-meta.xml @@ -0,0 +1,5 @@ + + + 49.0 + Active + diff --git a/src/classes/ContactHandler.cls b/src/classes/ContactHandler.cls new file mode 100644 index 00000000..84d27714 --- /dev/null +++ b/src/classes/ContactHandler.cls @@ -0,0 +1,37 @@ +public with sharing class ContactHandler extends TriggerHandler { + public override void afterInsert() { + Map contactsEnteringAccountsMap = new Map(); + + for (Contact c : (List)Trigger.new) { + if (c.AccountId != null) { + contactsEnteringAccountsMap.put(c.Id, c.AccountId); + } + } + + MembershipContactRoleService mcrs = new MembershipContactRoleService(); + + mcrs.addContactRolesForAddedContacts(contactsEnteringAccountsMap); + } + + public override void afterUpdate() { + // Handle reparented Contacts + Map contactsLeavingAccountsMap = new Map(); + Map contactsEnteringAccountsMap = new Map(); + + for (Contact c : (List)Trigger.new) { + Contact old = ((Map)Trigger.oldMap).get(c.Id); + if (c.AccountId != old.AccountId && c.AccountId != null) { + contactsEnteringAccountsMap.put(c.Id, c.AccountId); + } + if (c.AccountId != old.AccountId && old.AccountId != null) { + contactsLeavingAccountsMap.put(c.Id, old.AccountId); + } + } + + MembershipContactRoleService mcrs = new MembershipContactRoleService(); + + mcrs.deactivateContactRolesForMovedContacts(contactsLeavingAccountsMap); + mcrs.addContactRolesForAddedContacts(contactsEnteringAccountsMap); + + } +} diff --git a/src/classes/ContactHandler.cls-meta.xml b/src/classes/ContactHandler.cls-meta.xml new file mode 100644 index 00000000..8e4d11f8 --- /dev/null +++ b/src/classes/ContactHandler.cls-meta.xml @@ -0,0 +1,5 @@ + + + 49.0 + Active + diff --git a/src/classes/MembershipContactRoleService.cls b/src/classes/MembershipContactRoleService.cls new file mode 100644 index 00000000..899dd592 --- /dev/null +++ b/src/classes/MembershipContactRoleService.cls @@ -0,0 +1,88 @@ +// This class is `without sharing` to ensure data integrity +public without sharing class MembershipContactRoleService { + @TestVisible private ConfigurationService cs; + + public MembershipContactRoleService() { + cs = new ConfigurationService(); + } + + public void deactivateContactRolesForMovedContacts( + Map movedContactsFromHouseholdsMap + ) { + // Locate the Memberships associated with the former + // Households of these moved Contacts + + List applicableMembershipTypes = cs.getNonIndividualMembershipTypes(); + + List mcrs = [ + SELECT Id, Contact__c, Membership__r.Account__c + FROM Membership_Contact_Role__c + WHERE Contact__c IN :movedContactsFromHouseholdsMap.keySet() + AND Membership__r.Account__c IN :movedContactsFromHouseholdsMap.values() + AND Membership__r.Type__c IN :applicableMembershipTypes + ]; + List toUpdate = new List(); + + // Update their End Dates to today. + for (Membership_Contact_Role__c mcr : mcrs) { + // Ensure this is an affected MCR - we over-select and may get cross-household MCRs. + if (movedContactsFromHouseholdsMap.get(mcr.Contact__c) == mcr.Membership__r.Account__c) { + mcr.End_Date__c = Date.today(); + toUpdate.add(mcr); + } + } + + update mcrs; + } + + public void addContactRolesForAddedContacts( + Map addedContactsToHouseholdsMap + ) { + // Locate all Household-type memberships for the Households to which Contacts + // have been added. + + List applicableMembershipTypes = cs.getNonIndividualMembershipTypes(); + + List memberships = [ + SELECT Id, Name, Type__c, End_Date__c, Account__c + FROM Membership__c + WHERE Account__c IN :addedContactsToHouseholdsMap.values() + AND Type__c IN :applicableMembershipTypes + AND (Start_Date__c <= :Date.today() OR Start_Date__c = null) + AND (End_Date__c >= :Date.today() OR Does_Not_Expire__c = true) + ]; + + // Process them into a Map> keyed on Account Id + Map> membershipMap = new Map>(); + for (Membership__c m : memberships) { + if (!membershipMap.containsKey(m.Account__c)) { + membershipMap.put(m.Account__c, new List()); + } + + membershipMap.get(m.Account__c).add(m); + } + + // Iterate over Contacts and accumulate a list of new Membership Contact Roles. + List toInsert = new List(); + for (Id contactId : addedContactsToHouseholdsMap.keySet()) { + if (!membershipMap.containsKey(addedContactsToHouseholdsMap.get(contactId))) { + continue; + } + + for (Membership__c m : membershipMap.get(addedContactsToHouseholdsMap.get(contactId))) { + toInsert.add( + new Membership_Contact_Role__c( + Name = m.Name + ' ' + String.valueOf(Date.today().year()), + Contact__c = contactId, + Membership__c = m.Id, + Role__c = 'Household Member', + Start_Date__c = Date.today(), + End_Date__c = m.End_Date__c + ) + ); + } + } + + insert toInsert; + } +} diff --git a/src/classes/MembershipContactRoleService.cls-meta.xml b/src/classes/MembershipContactRoleService.cls-meta.xml new file mode 100644 index 00000000..8e4d11f8 --- /dev/null +++ b/src/classes/MembershipContactRoleService.cls-meta.xml @@ -0,0 +1,5 @@ + + + 49.0 + Active + diff --git a/src/classes/MembershipContactRoleService_TEST.cls b/src/classes/MembershipContactRoleService_TEST.cls new file mode 100644 index 00000000..f98bc569 --- /dev/null +++ b/src/classes/MembershipContactRoleService_TEST.cls @@ -0,0 +1,163 @@ +@isTest +private without sharing class MembershipContactRoleService_TEST { + @testSetup + private static void testSetup() { + List accounts = new List{ + new Account(Name = 'First Account'), + new Account(Name = 'Second Account'), + new Account(Name = 'Account Without Memberships') + }; + insert accounts; + + List memberships = new List{ + new Membership__c( + Name = 'First Membership', + Account__c = accounts[0].Id, + Start_Date__c = Date.today(), + Does_Not_Expire__c = true, + Type__c = 'Corporate' + ), + new Membership__c( + Name = 'Expired Membership', + Account__c = accounts[0].Id, + Start_Date__c = Date.today() - 10, + End_Date__c = Date.today() - 5, + Type__c = 'Corporate' + ), + new Membership__c( + Name = 'Second Membership', + Account__c = accounts[1].Id, + Start_Date__c = Date.today(), + Does_Not_Expire__c = true, + Type__c = 'Household' + ) + }; + insert memberships; + } + + @isTest + private static void testAddContactToAccountAddsMCRs() { + Test.startTest(); + List cs = new List{ + new Contact( + AccountId = [SELECT Id FROM Account WHERE Name = 'First Account'].Id, + FirstName = 'Molly', + LastName = 'Member' + ), + new Contact( + AccountId = [SELECT Id FROM Account WHERE Name = 'First Account'].Id, + FirstName = 'Mikaela', + LastName = 'Member' + ) + }; + insert cs; + + List mcrs = [ + SELECT Id, Name, Start_Date__c, Role__c, Membership__c, Contact__c + FROM Membership_Contact_Role__c + WHERE Contact__c = :cs[0].Id + ]; + System.assertEquals(1, mcrs.size(), 'expected 1 Role'); + System.assertEquals('First Membership ' + Date.today().year(), mcrs[0].Name); + System.assertEquals(Date.today(), mcrs[0].Start_Date__c); + System.assertEquals('Household Member', mcrs[0].Role__c); + + mcrs = [ + SELECT Id, Name, Start_Date__c, Role__c, Membership__c, Contact__c + FROM Membership_Contact_Role__c + WHERE Contact__c = :cs[1].Id + ]; + System.assertEquals(1, mcrs.size(), 'expected 1 Role'); + System.assertEquals('First Membership ' + Date.today().year(), mcrs[0].Name); + System.assertEquals(Date.today(), mcrs[0].Start_Date__c); + System.assertEquals('Household Member', mcrs[0].Role__c); + } + + @isTest + private static void testRemoveContactFromAccountDeactivatesMCRs() { + Test.startTest(); + List cs = new List{ + new Contact( + AccountId = [SELECT Id FROM Account WHERE Name = 'First Account'].Id, + FirstName = 'Molly', + LastName = 'Member' + ), + new Contact( + AccountId = [SELECT Id FROM Account WHERE Name = 'First Account'].Id, + FirstName = 'Mikaela', + LastName = 'Member' + ) + }; + insert cs; + + cs[0].AccountId = null; + cs[1].AccountId = null; + update cs; + + List mcrs = [ + SELECT Id, Name, Start_Date__c, End_Date__c, Role__c, Membership__c, Contact__c + FROM Membership_Contact_Role__c + WHERE Contact__c = :cs[0].Id + ]; + System.assertEquals(1, mcrs.size(), 'expected 1 Role'); + System.assertEquals('First Membership ' + Date.today().year(), mcrs[0].Name); + System.assertEquals(Date.today(), mcrs[0].Start_Date__c); + System.assertEquals(Date.today(), mcrs[0].End_Date__c); + System.assertEquals('Household Member', mcrs[0].Role__c); + + mcrs = [ + SELECT Id, Name, Start_Date__c, End_Date__c, Role__c, Membership__c, Contact__c + FROM Membership_Contact_Role__c + WHERE Contact__c = :cs[1].Id + ]; + System.assertEquals(1, mcrs.size(), 'expected 1 Role'); + System.assertEquals('First Membership ' + Date.today().year(), mcrs[0].Name); + System.assertEquals(Date.today(), mcrs[0].Start_Date__c); + System.assertEquals(Date.today(), mcrs[0].End_Date__c); + System.assertEquals('Household Member', mcrs[0].Role__c); + } + + @isTest + private static void testAddContactToAccountWithoutMemberships() { + Test.startTest(); + Contact c = new Contact( + AccountId = [SELECT Id FROM Account WHERE Name = 'Account Without Memberships'].Id, + LastName = 'Member' + ); + insert c; + + List mcrs = [ + SELECT Id + FROM Membership_Contact_Role__c + WHERE Contact__c = :c.Id + ]; + System.assertEquals(0, mcrs.size(), 'expected no Roles'); + } + + @isTest + private static void testRemoveContactFromAccountWithoutMemberships() { + Test.startTest(); + Contact c = new Contact( + AccountId = [SELECT Id FROM Account WHERE Name = 'Account Without Memberships'].Id, + LastName = 'Member' + ); + insert c; + + List mcrs = [ + SELECT Id + FROM Membership_Contact_Role__c + WHERE Contact__c = :c.Id + ]; + System.assertEquals(0, mcrs.size(), 'expected no Roles'); + + c.AccountId = null; + update c; + + mcrs = [ + SELECT Id + FROM Membership_Contact_Role__c + WHERE Contact__c = :c.Id + ]; + System.assertEquals(0, mcrs.size(), 'expected no Roles'); + } +} diff --git a/src/classes/MembershipContactRoleService_TEST.cls-meta.xml b/src/classes/MembershipContactRoleService_TEST.cls-meta.xml new file mode 100644 index 00000000..8e4d11f8 --- /dev/null +++ b/src/classes/MembershipContactRoleService_TEST.cls-meta.xml @@ -0,0 +1,5 @@ + + + 49.0 + Active + diff --git a/src/classes/MembershipHandler.cls b/src/classes/MembershipHandler.cls new file mode 100644 index 00000000..e291a18c --- /dev/null +++ b/src/classes/MembershipHandler.cls @@ -0,0 +1,126 @@ +/** + * Created by ChrisPifer on 9/24/2020. + */ + +public with sharing class MembershipHandler extends TriggerHandler { + + private Map newMembershipsMap; + private Map oldMembershipsMap; + private Map touchedRolesIds; + + public MembershipHandler(){ + this.newMembershipsMap = (Map) Trigger.newMap; + if (Trigger.oldMap != null) { + this.oldMembershipsMap = (Map) Trigger.oldMap; + } else { + this.oldMembershipsMap = new Map(); + } + } + + public override void afterInsert(){ + managePrimaryContactRole(); + } + + public override void afterUpdate(){ + managePrimaryContactRole(); + } + /** + * If when a membership is created or updated and primary contact is set or changed + * Check for a primary contact membership role for that contact, create if missing. + */ + + private void managePrimaryContactRole (){ + List membershipContacts = new List(); + Set newContactMembershipKey = new Set(); + Set oldContactMembershipKey = new Set(); + Set foundMembershipKeys = new Set(); + Set promotedMembershipKeys = new Set(); + Map keyPairsToPromote = new Map(); + Map keyPairsToDemote = new Map(); + List contactRolesForCreate = new List(); + List contactRolesForUpdate = new List(); + List contactRolesToDemote = new List(); + List contactRolesToPromote = new List(); + Map rolesByContactMembId = new Map(); + //check if we have any new records or changed contact roles + for(Id newMembershipId : this.newMembershipsMap.keySet()) { + Id newPrimContactId = newMembershipsMap.get(newMembershipId).Primary_Contact__c; + Id oldPrimContactId = oldMembershipsMap.containsKey(newMembershipId) ? oldMembershipsMap.get(newMembershipId).Primary_Contact__c : null; + //Three scenarios to manage - newly created, edited to new record, edited to null + newContactMembershipKey.add('' + newPrimContactId + newMembershipId); + oldContactMembershipKey.add('' + oldPrimContactId + newMembershipId); + //New Membership Record + if (oldPrimContactId == null && newPrimContactId != null) { + contactRolesForCreate.add(createRole(newPrimContactId, newMembershipId, true)); + } + //Updated membership, values changed + if (newPrimContactId != null && oldPrimContactId != null && newPrimContactId != oldPrimContactId) { + keyPairsToPromote.put(newPrimContactId, newMembershipId); + keyPairsToDemote.put(oldPrimContactId, newMembershipId); + promotedMembershipKeys.add('' + newPrimContactId + newMembershipId); + } + //Updated membership changed to null + if (newPrimContactId == null && oldPrimContactId != null) { + keyPairsToDemote.put(oldPrimContactId, newMembershipId); + } + } + contactRolesToDemote = [SELECT Id, Name, Contact__c, Membership__c FROM Membership_Contact_Role__c WHERE + Contact__c IN: keyPairsToDemote.keySet() AND Membership__c IN: keyPairsToDemote.values() ]; + contactRolesToPromote = [SELECT Id, Name, Contact__c, Membership__c FROM Membership_Contact_Role__c WHERE + Contact__c IN: keyPairsToPromote.keySet() AND Membership__c IN: keyPairsToPromote.values() ]; + for(Membership_Contact_Role__c mcr : contactRolesToPromote){ + foundMembershipKeys.add('' + mcr.Contact__c + mcr.Membership__c); + } + for(String idKey : promotedMembershipKeys){ + if(!foundMembershipKeys.contains(idKey)){ + contactRolesForCreate.add(createRole(idKey.left(18), idKey.right(18), true )); + } + } + for(Membership_Contact_Role__c mcr : contactRolesToDemote){ + String thisKey = '' + mcr.Contact__c + mcr.Membership__c; + if(oldContactMembershipKey.contains(thisKey)){ + contactRolesForUpdate.add(demoteRole(mcr)); + } + } + for(Membership_Contact_Role__c mcr : contactRolesToPromote){ + if(newContactMembershipKey.contains('' + mcr.Contact__c + mcr.Membership__c)) { + contactRolesForUpdate.add(promoteRole(mcr)); + } + } + update contactRolesForUpdate; + insert contactRolesForCreate; + } + + private Membership_Contact_Role__c createRole(Id ContactId, Id MembershipId, Boolean primary){ + String defaultName = ''; + if(primary){ + defaultName = 'Primary Member'; + } else { + defaultName = 'Member'; + } + + Membership_Contact_Role__c mcr = new Membership_Contact_Role__c( + Name = defaultName, + Contact__c = ContactId, + Membership__c = MembershipId, + Is_Primary__c = primary, + Start_Date__c = Date.today() + ); + return mcr; + } + + private Membership_Contact_Role__c demoteRole(Membership_Contact_Role__c mcr){ + if(mcr.name == 'Primary Member'){ + mcr.name = 'Member'; + } + mcr.Is_Primary__c = false; + return mcr; + } + + private Membership_Contact_Role__c promoteRole(Membership_Contact_Role__c mcr){ + mcr.Name = 'Primary Member'; + mcr.Is_Primary__c = true; + mcr.Start_Date__c = Date.today(); + return mcr; + } +} \ No newline at end of file diff --git a/src/classes/MembershipHandler.cls-meta.xml b/src/classes/MembershipHandler.cls-meta.xml new file mode 100644 index 00000000..db9bf8c6 --- /dev/null +++ b/src/classes/MembershipHandler.cls-meta.xml @@ -0,0 +1,5 @@ + + + 48.0 + Active + diff --git a/src/classes/MembershipHandler_TEST.cls b/src/classes/MembershipHandler_TEST.cls new file mode 100644 index 00000000..11c3308c --- /dev/null +++ b/src/classes/MembershipHandler_TEST.cls @@ -0,0 +1,135 @@ +@isTest +private without sharing class MembershipHandler_TEST { + @testSetup + private static void testSetup() { + List accounts = new List{ + new Account(Name = 'First Account') + }; + insert accounts; + + List contacts = new List{ + new Contact(LastName = 'Member', AccountId = accounts[0].Id), + new Contact(LastName = 'OtherMember', AccountId = accounts[0].Id) + }; + insert contacts; + + } + + @isTest + private static void testInsertMembershipWithPrimaryCreatesMCR() { + Membership__c m = new Membership__c( + Name = 'First Membership', + Account__c = [SELECT Id FROM Account].Id, + Primary_Contact__c = [SELECT Id FROM Contact WHERE LastName = 'Member'].Id, + Start_Date__c = Date.today(), + Does_Not_Expire__c = true, + Type__c = 'Corporate' + ); + insert m; + + List mcrs = [ + SELECT Id, Start_Date__c, Is_Primary__c + FROM Membership_Contact_Role__c + ]; + System.assertEquals(1, mcrs.size(), 'Expected 1 MCR'); + System.assert(mcrs[0].Is_Primary__c, 'Should be primary'); + System.assertEquals(Date.today(), mcrs[0].Start_Date__c); + } + + @isTest + private static void testUpdateMembershipWithNullPrimaryUpdatesMCR() { + Membership__c m = new Membership__c( + Name = 'First Membership', + Account__c = [SELECT Id FROM Account].Id, + Primary_Contact__c = [SELECT Id FROM Contact WHERE LastName = 'Member'].Id, + Start_Date__c = Date.today(), + Does_Not_Expire__c = true, + Type__c = 'Corporate' + ); + insert m; + + m.Primary_Contact__c = null; + update m; + + List mcrs = [ + SELECT Id, Start_Date__c, Is_Primary__c + FROM Membership_Contact_Role__c + ]; + System.assertEquals(1, mcrs.size(), 'Expected 1 MCR'); + System.assert(!mcrs[0].Is_Primary__c, 'Should not be primary'); + System.assertEquals(Date.today(), mcrs[0].Start_Date__c); + } + + @isTest + private static void testUpdateMembershipWithNewPrimaryUpdatesExistingMCRAndAddsNew() { + Id firstContactId = [SELECT Id FROM Contact WHERE LastName = 'Member'].Id; + Id secondContactId = [SELECT Id FROM Contact WHERE LastName = 'OtherMember'].Id; + Membership__c m = new Membership__c( + Name = 'First Membership', + Account__c = [SELECT Id FROM Account].Id, + Primary_Contact__c = firstContactId, + Start_Date__c = Date.today(), + Does_Not_Expire__c = true, + Type__c = 'Corporate' + ); + insert m; + + m.Primary_Contact__c = secondContactId; + update m; + + List mcrs = [ + SELECT Id, Start_Date__c, Is_Primary__c + FROM Membership_Contact_Role__c + WHERE Contact__c = :firstContactId + ]; + System.assertEquals(1, mcrs.size(), 'Expected 1 MCR'); + System.assert(!mcrs[0].Is_Primary__c, 'Should not be primary'); + System.assertEquals(Date.today(), mcrs[0].Start_Date__c); + + mcrs = [ + SELECT Id, Start_Date__c, Is_Primary__c + FROM Membership_Contact_Role__c + WHERE Contact__c = :secondContactId + ]; + System.assertEquals(1, mcrs.size(), 'Expected 1 MCR'); + System.assert(mcrs[0].Is_Primary__c, 'Should be primary'); + System.assertEquals(Date.today(), mcrs[0].Start_Date__c); + } + + private static void testUpdateMembershipWithNewPrimaryUpdatesExistingMCRs() { + Membership__c m = new Membership__c( + Name = 'First Membership', + Account__c = [SELECT Id FROM Account].Id, + Primary_Contact__c = [SELECT Id FROM Contact WHERE LastName = 'Member'].Id, + Start_Date__c = Date.today(), + Does_Not_Expire__c = true, + Type__c = 'Corporate' + ); + insert m; + insert new Membership_Contact_Role__c( + Membership__c = m.Id, + Contact__c = [SELECT Id FROM Contact WHERE LastName = 'OtherMember'].Id + ); + + m.Primary_Contact__c = [SELECT Id FROM Contact WHERE LastName = 'OtherMember'].Id; + update m; + + List mcrs = [ + SELECT Id, Start_Date__c, Is_Primary__c + FROM Membership_Contact_Role__c + WHERE Contact__c != :m.Primary_Contact__c + ]; + System.assertEquals(1, mcrs.size(), 'Expected 1 MCR'); + System.assert(!mcrs[0].Is_Primary__c, 'Should not be primary'); + System.assertEquals(Date.today(), mcrs[0].Start_Date__c); + + mcrs = [ + SELECT Id, Start_Date__c, Is_Primary__c + FROM Membership_Contact_Role__c + WHERE Contact__c = :m.Primary_Contact__c + ]; + System.assertEquals(1, mcrs.size(), 'Expected 1 MCR'); + System.assert(mcrs[0].Is_Primary__c, 'Should be primary'); + System.assertEquals(Date.today(), mcrs[0].Start_Date__c); + } +} \ No newline at end of file diff --git a/src/classes/MembershipHandler_TEST.cls-meta.xml b/src/classes/MembershipHandler_TEST.cls-meta.xml new file mode 100644 index 00000000..8e4d11f8 --- /dev/null +++ b/src/classes/MembershipHandler_TEST.cls-meta.xml @@ -0,0 +1,5 @@ + + + 49.0 + Active + diff --git a/src/classes/TriggerHandler.cls b/src/classes/TriggerHandler.cls new file mode 100644 index 00000000..887d1ddf --- /dev/null +++ b/src/classes/TriggerHandler.cls @@ -0,0 +1,240 @@ +public virtual class TriggerHandler { + + // static map of handlername, times run() was invoked + private static Map loopCountMap; + private static Set bypassedHandlers; + + // the current context of the trigger, overridable in tests + @TestVisible + private TriggerContext context; + + // the current context of the trigger, overridable in tests + @TestVisible + private Boolean isTriggerExecuting; + + // static initialization + static { + loopCountMap = new Map(); + bypassedHandlers = new Set(); + } + + // constructor + public TriggerHandler() { + this.setTriggerContext(); + } + + /*************************************** + * public instance methods + ***************************************/ + + // main method that will be called during execution + public void run() { + + if(!validateRun()) { + return; + } + + addToLoopCount(); + + // dispatch to the correct handler method + switch on this.context { + when BEFORE_INSERT { + this.beforeInsert(); + } + when BEFORE_UPDATE { + this.beforeUpdate(); + } + when BEFORE_DELETE { + this.beforeDelete(); + } + when AFTER_INSERT { + this.afterInsert(); + } + when AFTER_UPDATE { + this.afterUpdate(); + } + when AFTER_DELETE { + this.afterDelete(); + } + when AFTER_UNDELETE { + this.afterUndelete(); + } + } + } + + public void setMaxLoopCount(Integer max) { + String handlerName = getHandlerName(); + if(!TriggerHandler.loopCountMap.containsKey(handlerName)) { + TriggerHandler.loopCountMap.put(handlerName, new LoopCount(max)); + } else { + TriggerHandler.loopCountMap.get(handlerName).setMax(max); + } + } + + public void clearMaxLoopCount() { + this.setMaxLoopCount(-1); + } + + /*************************************** + * public static methods + ***************************************/ + + public static void bypass(String handlerName) { + TriggerHandler.bypassedHandlers.add(handlerName); + } + + public static void clearBypass(String handlerName) { + TriggerHandler.bypassedHandlers.remove(handlerName); + } + + public static Boolean isBypassed(String handlerName) { + return TriggerHandler.bypassedHandlers.contains(handlerName); + } + + public static void clearAllBypasses() { + TriggerHandler.bypassedHandlers.clear(); + } + + /*************************************** + * private instancemethods + ***************************************/ + + @TestVisible + private void setTriggerContext() { + this.setTriggerContext(null, false); + } + + @TestVisible + private void setTriggerContext(String ctx, Boolean testMode) { + if(!Trigger.isExecuting && !testMode) { + this.isTriggerExecuting = false; + return; + } else { + this.isTriggerExecuting = true; + } + + if((Trigger.isExecuting && Trigger.isBefore && Trigger.isInsert) || + (ctx != null && ctx == 'before insert')) { + this.context = TriggerContext.BEFORE_INSERT; + } else if((Trigger.isExecuting && Trigger.isBefore && Trigger.isUpdate) || + (ctx != null && ctx == 'before update')){ + this.context = TriggerContext.BEFORE_UPDATE; + } else if((Trigger.isExecuting && Trigger.isBefore && Trigger.isDelete) || + (ctx != null && ctx == 'before delete')) { + this.context = TriggerContext.BEFORE_DELETE; + } else if((Trigger.isExecuting && Trigger.isAfter && Trigger.isInsert) || + (ctx != null && ctx == 'after insert')) { + this.context = TriggerContext.AFTER_INSERT; + } else if((Trigger.isExecuting && Trigger.isAfter && Trigger.isUpdate) || + (ctx != null && ctx == 'after update')) { + this.context = TriggerContext.AFTER_UPDATE; + } else if((Trigger.isExecuting && Trigger.isAfter && Trigger.isDelete) || + (ctx != null && ctx == 'after delete')) { + this.context = TriggerContext.AFTER_DELETE; + } else if((Trigger.isExecuting && Trigger.isAfter && Trigger.isUndelete) || + (ctx != null && ctx == 'after undelete')) { + this.context = TriggerContext.AFTER_UNDELETE; + } + } + + // increment the loop count + @TestVisible + private void addToLoopCount() { + String handlerName = getHandlerName(); + if(TriggerHandler.loopCountMap.containsKey(handlerName)) { + Boolean exceeded = TriggerHandler.loopCountMap.get(handlerName).increment(); + if(exceeded) { + Integer max = TriggerHandler.loopCountMap.get(handlerName).max; + throw new TriggerHandlerException('Maximum loop count of ' + String.valueOf(max) + ' reached in ' + handlerName); + } + } + } + + // make sure this trigger should continue to run + @TestVisible + private Boolean validateRun() { + if(!this.isTriggerExecuting || this.context == null) { + throw new TriggerHandlerException('Trigger handler called outside of Trigger execution'); + } + return !TriggerHandler.bypassedHandlers.contains(getHandlerName()); + } + + @TestVisible + private String getHandlerName() { + return String.valueOf(this).substring(0,String.valueOf(this).indexOf(':')); + } + + /*************************************** + * context methods + ***************************************/ + + // context-specific methods for override + @TestVisible + protected virtual void beforeInsert(){} + @TestVisible + protected virtual void beforeUpdate(){} + @TestVisible + protected virtual void beforeDelete(){} + @TestVisible + protected virtual void afterInsert(){} + @TestVisible + protected virtual void afterUpdate(){} + @TestVisible + protected virtual void afterDelete(){} + @TestVisible + protected virtual void afterUndelete(){} + + /*************************************** + * inner classes + ***************************************/ + + // inner class for managing the loop count per handler + @TestVisible + private class LoopCount { + private Integer max; + private Integer count; + + public LoopCount() { + this.max = 5; + this.count = 0; + } + + public LoopCount(Integer max) { + this.max = max; + this.count = 0; + } + + public Boolean increment() { + this.count++; + return this.exceeded(); + } + + public Boolean exceeded() { + return this.max >= 0 && this.count > this.max; + } + + public Integer getMax() { + return this.max; + } + + public Integer getCount() { + return this.count; + } + + public void setMax(Integer max) { + this.max = max; + } + } + + // possible trigger contexts + @TestVisible + private enum TriggerContext { + BEFORE_INSERT, BEFORE_UPDATE, BEFORE_DELETE, + AFTER_INSERT, AFTER_UPDATE, AFTER_DELETE, + AFTER_UNDELETE + } + + // exception class + public class TriggerHandlerException extends Exception {} + +} \ No newline at end of file diff --git a/src/classes/TriggerHandler.cls-meta.xml b/src/classes/TriggerHandler.cls-meta.xml new file mode 100644 index 00000000..db9bf8c6 --- /dev/null +++ b/src/classes/TriggerHandler.cls-meta.xml @@ -0,0 +1,5 @@ + + + 48.0 + Active + diff --git a/src/classes/TriggerHandler_Test.cls b/src/classes/TriggerHandler_Test.cls new file mode 100644 index 00000000..68185e13 --- /dev/null +++ b/src/classes/TriggerHandler_Test.cls @@ -0,0 +1,281 @@ +/** + * Created by ChrisPifer on 9/24/2020. + */ + +@isTest +private class TriggerHandler_Test { + + private static final String TRIGGER_CONTEXT_ERROR = 'Trigger handler called outside of Trigger execution'; + + private static String lastMethodCalled; + + private static TriggerHandler_Test.TestHandler handler; + + static { + handler = new TriggerHandler_Test.TestHandler(); + // override its internal trigger detection + handler.isTriggerExecuting = true; + } + + /*************************************** + * unit tests + ***************************************/ + + // contexts tests + + @isTest + static void testBeforeInsert() { + beforeInsertMode(); + handler.run(); + System.assertEquals('beforeInsert', lastMethodCalled, 'last method should be beforeInsert'); + } + + @isTest + static void testBeforeUpdate() { + beforeUpdateMode(); + handler.run(); + System.assertEquals('beforeUpdate', lastMethodCalled, 'last method should be beforeUpdate'); + } + + @isTest + static void testBeforeDelete() { + beforeDeleteMode(); + handler.run(); + System.assertEquals('beforeDelete', lastMethodCalled, 'last method should be beforeDelete'); + } + + @isTest + static void testAfterInsert() { + afterInsertMode(); + handler.run(); + System.assertEquals('afterInsert', lastMethodCalled, 'last method should be afterInsert'); + } + + @isTest + static void testAfterUpdate() { + afterUpdateMode(); + handler.run(); + System.assertEquals('afterUpdate', lastMethodCalled, 'last method should be afterUpdate'); + } + + @isTest + static void testAfterDelete() { + afterDeleteMode(); + handler.run(); + System.assertEquals('afterDelete', lastMethodCalled, 'last method should be afterDelete'); + } + + @isTest + static void testAfterUndelete() { + afterUndeleteMode(); + handler.run(); + System.assertEquals('afterUndelete', lastMethodCalled, 'last method should be afterUndelete'); + } + + @isTest + static void testNonTriggerContext() { + try{ + handler.run(); + System.assert(false, 'the handler ran but should have thrown'); + } catch(TriggerHandler.TriggerHandlerException te) { + System.assertEquals(TRIGGER_CONTEXT_ERROR, te.getMessage(), 'the exception message should match'); + } catch(Exception e) { + System.assert(false, 'the exception thrown was not expected: ' + e.getTypeName() + ': ' + e.getMessage()); + } + } + + // test bypass api + + @isTest + static void testBypassAPI() { + afterUpdateMode(); + + // test a bypass and run handler + TriggerHandler.bypass('TestHandler'); + handler.run(); + System.assertEquals(null, lastMethodCalled, 'last method should be null when bypassed'); + System.assertEquals(true, TriggerHandler.isBypassed('TestHandler'), 'test handler should be bypassed'); + resetTest(); + + // clear that bypass and run handler + TriggerHandler.clearBypass('TestHandler'); + handler.run(); + System.assertEquals('afterUpdate', lastMethodCalled, 'last method called should be afterUpdate'); + System.assertEquals(false, TriggerHandler.isBypassed('TestHandler'), 'test handler should be bypassed'); + resetTest(); + + // test a re-bypass and run handler + TriggerHandler.bypass('TestHandler'); + handler.run(); + System.assertEquals(null, lastMethodCalled, 'last method should be null when bypassed'); + System.assertEquals(true, TriggerHandler.isBypassed('TestHandler'), 'test handler should be bypassed'); + resetTest(); + + // clear all bypasses and run handler + TriggerHandler.clearAllBypasses(); + handler.run(); + System.assertEquals('afterUpdate', lastMethodCalled, 'last method called should be afterUpdate'); + System.assertEquals(false, TriggerHandler.isBypassed('TestHandler'), 'test handler should be bypassed'); + resetTest(); + } + + // instance method tests + + @isTest + static void testLoopCount() { + beforeInsertMode(); + + // set the max loops to 2 + handler.setMaxLoopCount(2); + + // run the handler twice + handler.run(); + handler.run(); + + // clear the tests + resetTest(); + + try { + // try running it. This should exceed the limit. + handler.run(); + System.assert(false, 'the handler should throw on the 3rd run when maxloopcount is 3'); + } catch(TriggerHandler.TriggerHandlerException te) { + // we're expecting to get here + System.assertEquals(null, lastMethodCalled, 'last method should be null'); + } catch(Exception e) { + System.assert(false, 'the exception thrown was not expected: ' + e.getTypeName() + ': ' + e.getMessage()); + } + + // clear the tests + resetTest(); + + // now clear the loop count + handler.clearMaxLoopCount(); + + try { + // re-run the handler. We shouldn't throw now. + handler.run(); + System.assertEquals('beforeInsert', lastMethodCalled, 'last method should be beforeInsert'); + } catch(TriggerHandler.TriggerHandlerException te) { + System.assert(false, 'running the handler after clearing the loop count should not throw'); + } catch(Exception e) { + System.assert(false, 'the exception thrown was not expected: ' + e.getTypeName() + ': ' + e.getMessage()); + } + } + + @isTest + static void testLoopCountClass() { + TriggerHandler.LoopCount lc = new TriggerHandler.LoopCount(); + System.assertEquals(5, lc.getMax(), 'max should be five on init'); + System.assertEquals(0, lc.getCount(), 'count should be zero on init'); + + lc.increment(); + System.assertEquals(1, lc.getCount(), 'count should be 1'); + System.assertEquals(false, lc.exceeded(), 'should not be exceeded with count of 1'); + + lc.increment(); + lc.increment(); + lc.increment(); + lc.increment(); + System.assertEquals(5, lc.getCount(), 'count should be 5'); + System.assertEquals(false, lc.exceeded(), 'should not be exceeded with count of 5'); + + lc.increment(); + System.assertEquals(6, lc.getCount(), 'count should be 6'); + System.assertEquals(true, lc.exceeded(), 'should not be exceeded with count of 6'); + } + + // private method tests + + @isTest + static void testGetHandlerName() { + System.assertEquals('TestHandler', handler.getHandlerName(), 'handler name should match class name'); + } + + // test virtual methods + + @isTest + static void testVirtualMethods() { + TriggerHandler h = new TriggerHandler(); + h.beforeInsert(); + h.beforeUpdate(); + h.beforeDelete(); + h.afterInsert(); + h.afterUpdate(); + h.afterDelete(); + h.afterUndelete(); + } + + /*************************************** + * testing utilities + ***************************************/ + + private static void resetTest() { + lastMethodCalled = null; + } + + // modes for testing + + private static void beforeInsertMode() { + handler.setTriggerContext('before insert', true); + } + + private static void beforeUpdateMode() { + handler.setTriggerContext('before update', true); + } + + private static void beforeDeleteMode() { + handler.setTriggerContext('before delete', true); + } + + private static void afterInsertMode() { + handler.setTriggerContext('after insert', true); + } + + private static void afterUpdateMode() { + handler.setTriggerContext('after update', true); + } + + private static void afterDeleteMode() { + handler.setTriggerContext('after delete', true); + } + + private static void afterUndeleteMode() { + handler.setTriggerContext('after undelete', true); + } + + // test implementation of the TriggerHandler + + private class TestHandler extends TriggerHandler { + + public override void beforeInsert() { + TriggerHandler_Test.lastMethodCalled = 'beforeInsert'; + } + + public override void beforeUpdate() { + TriggerHandler_Test.lastMethodCalled = 'beforeUpdate'; + } + + public override void beforeDelete() { + TriggerHandler_Test.lastMethodCalled = 'beforeDelete'; + } + + public override void afterInsert() { + TriggerHandler_Test.lastMethodCalled = 'afterInsert'; + } + + public override void afterUpdate() { + TriggerHandler_Test.lastMethodCalled = 'afterUpdate'; + } + + public override void afterDelete() { + TriggerHandler_Test.lastMethodCalled = 'afterDelete'; + } + + public override void afterUndelete() { + TriggerHandler_Test.lastMethodCalled = 'afterUndelete'; + } + + } + +} \ No newline at end of file diff --git a/src/classes/TriggerHandler_Test.cls-meta.xml b/src/classes/TriggerHandler_Test.cls-meta.xml new file mode 100644 index 00000000..db9bf8c6 --- /dev/null +++ b/src/classes/TriggerHandler_Test.cls-meta.xml @@ -0,0 +1,5 @@ + + + 48.0 + Active + diff --git a/src/flexipages/Membership_UtilityBar.flexipage b/src/flexipages/Membership_UtilityBar.flexipage new file mode 100644 index 00000000..fd8be54c --- /dev/null +++ b/src/flexipages/Membership_UtilityBar.flexipage @@ -0,0 +1,16 @@ + + + + utilityItems + Region + + + backgroundComponents + Background + + Membership UtilityBar + + UtilityBar + diff --git a/src/globalValueSets/Membership_Status.globalValueSet b/src/globalValueSets/Membership_Status.globalValueSet new file mode 100644 index 00000000..9da02da0 --- /dev/null +++ b/src/globalValueSets/Membership_Status.globalValueSet @@ -0,0 +1,25 @@ + + + + Former + false + + + + Lapsed + false + + + + Renewal + false + + + + Current + false + + + Membership Status + false + diff --git a/src/globalValueSets/Membership_Type.globalValueSet b/src/globalValueSets/Membership_Type.globalValueSet new file mode 100644 index 00000000..e6756256 --- /dev/null +++ b/src/globalValueSets/Membership_Type.globalValueSet @@ -0,0 +1,20 @@ + + + + Individual + false + + + + Household + false + + + + Corporate + false + + + Membership Type + false + diff --git a/src/layouts/Membership_Contact_Role__c-Membership Contact Role Layout.layout b/src/layouts/Membership_Contact_Role__c-Membership Contact Role Layout.layout new file mode 100644 index 00000000..83e25cb0 --- /dev/null +++ b/src/layouts/Membership_Contact_Role__c-Membership Contact Role Layout.layout @@ -0,0 +1,108 @@ + + + + false + false + true + + + + Required + Name + + + Required + Membership__c + + + Required + Contact__c + + + Edit + Role__c + + + Edit + Is_Primary__c + + + Edit + Start_Date__c + + + Edit + Does_Not_Expire__c + + + Edit + Status__c + + + Edit + Type__c + + + + + + + false + false + true + + + + Readonly + CreatedById + + + + + Readonly + LastModifiedById + + + + + + true + false + true + + + + + + + + TASK.SUBJECT + TASK.WHO_NAME + ACTIVITY.TASK + TASK.DUE_DATE + TASK.STATUS + TASK.PRIORITY + CORE.USERS.FULL_NAME + RelatedActivityList + + + TASK.SUBJECT + TASK.WHO_NAME + ACTIVITY.TASK + TASK.DUE_DATE + CORE.USERS.FULL_NAME + TASK.LAST_UPDATE + RelatedHistoryList + + false + false + false + false + false + + 00h17000003Kbk1 + 4 + 0 + Default + + diff --git a/src/layouts/Membership__c-Membership Layout.layout b/src/layouts/Membership__c-Membership Layout.layout new file mode 100644 index 00000000..78924719 --- /dev/null +++ b/src/layouts/Membership__c-Membership Layout.layout @@ -0,0 +1,248 @@ + + + + false + false + true + + + + Required + Name + + + Edit + Primary_Contact__c + + + Edit + Account__c + + + Edit + Product__c + + + Edit + Opportunity__c + + + Edit + Status__c + + + + + Edit + OwnerId + + + Edit + Type__c + + + Edit + Start_Date__c + + + Edit + End_Date__c + + + Edit + Does_Not_Expire__c + + + true + + + Edit + Origin__c + + + + + + false + false + true + + + + Readonly + CreatedById + + + + + Readonly + LastModifiedById + + + + + + true + false + true + + + + + + + + Record + + FeedItem.TextPost + QuickAction + 0 + + + FeedItem.ContentPost + QuickAction + 1 + + + Membership__c.New_Membership_Contact_Role + QuickAction + 2 + + + NewEvent + QuickAction + 3 + + + NewTask + QuickAction + 4 + + + NewContact + QuickAction + 5 + + + LogACall + QuickAction + 6 + + + NewOpportunity + QuickAction + 7 + + + NewCase + QuickAction + 8 + + + NewLead + QuickAction + 9 + + + FeedItem.RypplePost + QuickAction + 10 + + + FeedItem.LinkPost + QuickAction + 11 + + + FeedItem.PollPost + QuickAction + 12 + + + FeedItem.QuestionPost + QuickAction + 13 + + + Share + StandardButton + 14 + + + ChangeOwnerOne + StandardButton + 15 + + + Clone + StandardButton + 16 + + + ChangeRecordType + StandardButton + 17 + + + PrintableView + StandardButton + 18 + + + Delete + StandardButton + 19 + + + Edit + StandardButton + 20 + + + + TASK.SUBJECT + TASK.WHO_NAME + ACTIVITY.TASK + TASK.DUE_DATE + TASK.STATUS + TASK.PRIORITY + CORE.USERS.FULL_NAME + RelatedActivityList + + + TASK.SUBJECT + TASK.WHO_NAME + ACTIVITY.TASK + TASK.DUE_DATE + CORE.USERS.FULL_NAME + TASK.LAST_UPDATE + RelatedHistoryList + + + NAME + Is_Primary__c + Role__c + Status__c + Type__c + Membership_Contact_Role__c.Membership__c + + + Product2 + Quantity + UnitPrice + ServiceDate + Description + OpportunityLineItem.Membership__c + + false + false + false + false + false + + 00h0R000002Zypw + 4 + 0 + Default + + diff --git a/src/layouts/OpportunityLineItem-Opportunity Product Layout.layout b/src/layouts/OpportunityLineItem-Opportunity Product Layout.layout new file mode 100644 index 00000000..24f7528d --- /dev/null +++ b/src/layouts/OpportunityLineItem-Opportunity Product Layout.layout @@ -0,0 +1,98 @@ + + + + false + false + true + + + + Required + OpportunityId + + + Required + Product2Id + + + Readonly + ProductCode + + + Readonly + ListPrice + + + Required + UnitPrice + + + Required + Quantity + + + Edit + Membership__c + + + + + Edit + ServiceDate + + + Readonly + TotalPrice + + + + + + false + false + true + + + Readonly + CreatedById + + + + + Readonly + LastModifiedById + + + + + + false + false + false + + + Edit + Description + + + + + + false + false + true + + + + + + Product2Id + Quantity + UnitPrice + ServiceDate + Description + OpportunityId + false + false + false + diff --git a/src/objects/Account.object b/src/objects/Account.object new file mode 100644 index 00000000..79dfebcf --- /dev/null +++ b/src/objects/Account.object @@ -0,0 +1,102 @@ + + + + + First_Membership_Start_Date__c + + false + + The Start Date of the oldest Membership associated with this Account + + + + false + + false + + Date + + + + + Last_Membership_Start_Date__c + + false + + The Start Date of the most recent Membership associated to this Account + + + + false + + false + + Date + + + + + Membership_End_Date__c + + false + + The greatest End Date of any Membership associated with this Account + + + + false + + false + + Date + + + + + Membership_Status__c + + false + + + + false + + false + + Picklist + + + + true + + Membership_Status + + + + + + + Membership_Type__c + + false + + This Account's current Membership Type, set based on the Type of the related Membership with the greatest End Date + + + + false + + false + + Picklist + + + + true + + Membership_Type + + + + + diff --git a/src/objects/Contact.object b/src/objects/Contact.object new file mode 100644 index 00000000..b138a9df --- /dev/null +++ b/src/objects/Contact.object @@ -0,0 +1,104 @@ + + + + + First_Membership_Start_Date__c + + false + + The start date of the oldest Membership associated to this Contact + + + + false + + false + + Date + + + + + Last_Membership_Start_Date__c + + false + + The start date of the most recent Membership associated with this Contact + + + + false + + false + + Date + + + + + Membership_End_Date__c + + false + + The greatest End Date of any Membership Contact Role associated with this Contact + + + + false + + false + + Date + + + + + Membership_Status__c + + false + + The Contact's current memberships status + + + + false + + false + + Picklist + + + + true + + Membership_Status + + + + + + + Membership_Type__c + + false + + This Contact's current Membership Type, based on the most recent active Membership + + + + false + + false + + Picklist + + + + true + + Membership_Type + + + + + diff --git a/src/objects/Membership_Contact_Role__c.object b/src/objects/Membership_Contact_Role__c.object new file mode 100644 index 00000000..ecafd40f --- /dev/null +++ b/src/objects/Membership_Contact_Role__c.object @@ -0,0 +1,599 @@ + + + + + + Accept + + Default + + + + + + Accept + + Large + + Default + + + + + + Accept + + Small + + Default + + + + + + CancelEdit + + Default + + + + + + CancelEdit + + Large + + Default + + + + + + CancelEdit + + Small + + Default + + + + + + Clone + + Default + + + + + + Clone + + Large + + Default + + + + + + Clone + + Small + + Default + + + + + + Delete + + Default + + + + + + Delete + + Large + + Default + + + + + + Delete + + Small + + Default + + + + + + Edit + + Default + + + + + + Edit + + Large + + Default + + + + + + Edit + + Small + + Default + + + + + + List + + Default + + + + + + List + + Large + + Default + + + + + + List + + Small + + Default + + + + + + New + + Default + + + + + + New + + Large + + Default + + + + + + New + + Small + + Default + + + + + + SaveEdit + + Default + + + + + + SaveEdit + + Large + + Default + + + + + + SaveEdit + + Small + + Default + + + + + + Tab + + Default + + + + + + Tab + + Large + + Default + + + + + + Tab + + Small + + Default + + + + + + View + + Default + + + + + + View + + Large + + Default + + + + + + View + + Small + + Default + + + + true + + SYSTEM + + Deployed + + Can represent the Members themselves that belong to a membership OR anyone involved with the Membership, using picklist values and other fields to define the role. + + true + + true + + false + + true + + false + + true + + true + + true + + true + + ControlledByParent + + + + + + + + false + + Text + + + + Membership Contact Roles + + + + ControlledByParent + + Public + + + + Contact__c + + The Contact record who holds the Membership + + false + + The Member Contact record + + + + Contact + + Memberships + + Membership_Contact_Roles + + 1 + + false + + false + + false + + MasterDetail + + false + + + + + Does_Not_Expire__c + + false + + false + + This Contact's Membership does not expire. Defaults to the expiry setting on the Membership. + + + + false + + false + + Checkbox + + + + + End_Date__c + + false + + + + false + + false + + false + + Date + + + + + Is_Primary__c + + false + + This Membership Contact is the primary point person for the Membership + + false + + This Membership Contact is the primary point person for the Membership + + + + false + + false + + Checkbox + + + + + Membership__c + + The Membership record to which the Member belongs + + false + + The Membership record to which the Member belongs + + + + Membership__c + + Membership Contact Roles + + Membership_Contact_Roles + + 0 + + false + + false + + false + + MasterDetail + + false + + + + + Role__c + + false + + + + false + + false + + false + + Picklist + + + + true + + + + false + + + + Named Member + + false + + + + + + + + Additional Member + + false + + + + + + + + Spouse + + false + + + + + + + + Household Member + + false + + + + + + + + Employee + + false + + + + + + + + + + + + + Start_Date__c + + false + + + + false + + false + + false + + Date + + + + + Status__c + + false + + + + false + + false + + false + + Picklist + + + + true + + Membership_Status + + + + + + + Type__c + + false + + The Type of Membership this Contact has + + + + false + + false + + false + + Picklist + + + + true + + Membership_Type + + + + + diff --git a/src/objects/Membership__c.object b/src/objects/Membership__c.object new file mode 100644 index 00000000..d279b8da --- /dev/null +++ b/src/objects/Membership__c.object @@ -0,0 +1,650 @@ + + + + + + Accept + + Default + + + + + + Accept + + Large + + Default + + + + + + Accept + + Small + + Default + + + + + + CancelEdit + + Default + + + + + + CancelEdit + + Large + + Default + + + + + + CancelEdit + + Small + + Default + + + + + + Clone + + Default + + + + + + Clone + + Large + + Default + + + + + + Clone + + Small + + Default + + + + + + Delete + + Default + + + + + + Delete + + Large + + Default + + + + + + Delete + + Small + + Default + + + + + + Edit + + Default + + + + + + Edit + + Large + + Default + + + + + + Edit + + Small + + Default + + + + + + List + + Default + + + + + + List + + Large + + Default + + + + + + List + + Small + + Default + + + + + + New + + Default + + + + + + New + + Large + + Default + + + + + + New + + Small + + Default + + + + + + SaveEdit + + Default + + + + + + SaveEdit + + Large + + Default + + + + + + SaveEdit + + Small + + Default + + + + + + Tab + + Default + + + + + + Tab + + Large + + Default + + + + + + Tab + + Small + + Default + + + + + + View + + Default + + + + + + View + + Large + + Default + + + + + + View + + Small + + Default + + + + true + + SYSTEM + + Deployed + + Object to hold memberships and related items (sponsorships, benefits, etc) + + true + + true + + false + + true + + false + + true + + true + + true + + true + + Private + + + + + + + + false + + Text + + + + Memberships + + + + ReadWrite + + Public + + + + Account__c + + SetNull + + false + + + + Account + + Memberships + + Memberships + + false + + false + + false + + Lookup + + + + + Does_Not_Expire__c + + false + + false + + + + false + + false + + Checkbox + + + + + End_Date__c + + false + + + + false + + false + + false + + Date + + + + + Opportunity__c + + SetNull + + false + + + + Opportunity + + Memberships + + Memberships + + false + + false + + false + + Lookup + + + + + Origin__c + + false + + + + false + + false + + false + + Picklist + + + + true + + + + false + + + + New + + false + + + + + + + + Renewed + + false + + + + + + + + Reacquired + + false + + + + + + + + + + + + + Primary_Contact__c + + SetNull + + false + + + + Contact + + Memberships + + Memberships + + false + + false + + false + + Lookup + + + + + Product__c + + SetNull + + false + + + + Product2 + + Memberships + + Memberships + + false + + false + + false + + Lookup + + + + + Start_Date__c + + false + + + + false + + false + + false + + Date + + + + + Status__c + + false + + + + false + + false + + false + + Picklist + + + + true + + Membership_Status + + + + + + + Type__c + + false + + + + false + + false + + false + + Picklist + + + + true + + + + false + + + + Individual + + false + + + + + + + + Household + + false + + + + + + + + Corporate + + false + + + + + + + + + + + + + End_Date_or_Does_Not_Expire + + true + + A membership must either have an End Date or have Does Not Expire selected. + + NOT(Does_Not_Expire__c) && ISBLANK( End_Date__c ) + + End_Date__c + + Please populate the End Date or select Does Not Expire. + + + + + All + + Everything + + + + + diff --git a/src/objects/OpportunityLineItem.object b/src/objects/OpportunityLineItem.object new file mode 100644 index 00000000..292d1b07 --- /dev/null +++ b/src/objects/OpportunityLineItem.object @@ -0,0 +1,26 @@ + + + + + Membership__c + + SetNull + + Memberships related to this product + + false + + + + Membership__c + + Opportunity Product + + Opportunity_Product + + false + + Lookup + + + diff --git a/src/package.xml b/src/package.xml new file mode 100644 index 00000000..c900e375 --- /dev/null +++ b/src/package.xml @@ -0,0 +1,111 @@ + + + Membership-Management + + ConfigurationService + ContactHandler + MembershipContactRoleService + MembershipContactRoleService_TEST + MembershipHandler + MembershipHandler_TEST + TriggerHandler + TriggerHandler_Test + ApexClass + + + ContactTrigger + MembershipTrigger + ApexTrigger + + + Membership + CustomApplication + + + Account.First_Membership_Start_Date__c + Account.Last_Membership_Start_Date__c + Account.Membership_End_Date__c + Account.Membership_Status__c + Account.Membership_Type__c + Contact.First_Membership_Start_Date__c + Contact.Last_Membership_Start_Date__c + Contact.Membership_End_Date__c + Contact.Membership_Status__c + Contact.Membership_Type__c + Membership_Contact_Role__c.Contact__c + Membership_Contact_Role__c.Does_Not_Expire__c + Membership_Contact_Role__c.End_Date__c + Membership_Contact_Role__c.Is_Primary__c + Membership_Contact_Role__c.Membership__c + Membership_Contact_Role__c.Role__c + Membership_Contact_Role__c.Start_Date__c + Membership_Contact_Role__c.Status__c + Membership_Contact_Role__c.Type__c + Membership__c.Account__c + Membership__c.Does_Not_Expire__c + Membership__c.End_Date__c + Membership__c.Opportunity__c + Membership__c.Origin__c + Membership__c.Primary_Contact__c + Membership__c.Product__c + Membership__c.Start_Date__c + Membership__c.Status__c + Membership__c.Type__c + OpportunityLineItem.Membership__c + CustomField + + + Membership_Contact_Role__c + Membership__c + CustomObject + + + Membership__c + CustomTab + + + Membership_UtilityBar + FlexiPage + + + Membership_Status + Membership_Type + GlobalValueSet + + + Membership_Contact_Role__c-Membership Contact Role Layout + Membership__c-Membership Layout + OpportunityLineItem-Opportunity Product Layout + Layout + + + Membership__c.All + ListView + + + Manage_Memberships + PermissionSet + + + Account.New_Corporate_Membership + Account.New_Membership + Contact.New_Membership + Membership__c.New_Membership_Contact_Role + Opportunity.New_Membership + QuickAction + + + Memberships + Memberships/Accounts_w_Memberships_but_no_Contacts_ZBx + Report + + + Accounts_with_Memberships + ReportType + + + Membership__c.End_Date_or_Does_Not_Expire + ValidationRule + + 48.0 + \ No newline at end of file diff --git a/src/permissionsets/Manage_Memberships.permissionset b/src/permissionsets/Manage_Memberships.permissionset new file mode 100644 index 00000000..50a58e11 --- /dev/null +++ b/src/permissionsets/Manage_Memberships.permissionset @@ -0,0 +1,96 @@ + + + + true + Membership_Contact_Role__c.Is_Primary__c + true + + + true + Membership_Contact_Role__c.Role__c + true + + + true + Membership_Contact_Role__c.Start_Date__c + true + + + true + Membership_Contact_Role__c.End_Date__c + true + + + true + Membership__c.Account__c + true + + + true + Membership__c.End_Date__c + true + + + true + Membership__c.Opportunity__c + true + + + true + Membership__c.Origin__c + true + + + true + Membership__c.Primary_Contact__c + true + + + true + Membership__c.Product__c + true + + + true + Membership__c.Start_Date__c + true + + + true + Membership__c.Type__c + true + + false + + + false + false + false + true + false + Contact + false + + + true + true + true + true + false + Membership_Contact_Role__c + false + + + true + true + true + true + false + Membership__c + false + + + Membership__c + Visible + + diff --git a/src/quickActions/Account.New_Corporate_Membership.quickAction b/src/quickActions/Account.New_Corporate_Membership.quickAction new file mode 100644 index 00000000..a840618f --- /dev/null +++ b/src/quickActions/Account.New_Corporate_Membership.quickAction @@ -0,0 +1,37 @@ + + + + Name + Account.Name & "- Corporate Membership" + + + Type__c + Corporate + + + true + + TwoColumnsLeftToRight + + + false + Start_Date__c + Edit + + + false + End_Date__c + Edit + + + false + Does_Not_Expire__c + Edit + + + + + Membership__c + Account__c + Create + diff --git a/src/quickActions/Account.New_Membership.quickAction b/src/quickActions/Account.New_Membership.quickAction new file mode 100644 index 00000000..b32cc08d --- /dev/null +++ b/src/quickActions/Account.New_Membership.quickAction @@ -0,0 +1,33 @@ + + + + Name + Account.Name & "- Family Membership" + + + true + + TwoColumnsLeftToRight + + + false + Start_Date__c + Edit + + + false + End_Date__c + Edit + + + false + Does_Not_Expire__c + Edit + + + + + Membership__c + Account__c + Create + diff --git a/src/quickActions/Contact.New_Membership.quickAction b/src/quickActions/Contact.New_Membership.quickAction new file mode 100644 index 00000000..d7b665be --- /dev/null +++ b/src/quickActions/Contact.New_Membership.quickAction @@ -0,0 +1,41 @@ + + + + Account__c + Contact.AccountId + + + Name + Contact.LastName & "- Individual Membership" + + + Type__c + Individual + + + true + + TwoColumnsLeftToRight + + + false + Start_Date__c + Edit + + + false + End_Date__c + Edit + + + false + Does_Not_Expire__c + Edit + + + + + Membership__c + Primary_Contact__c + Create + diff --git a/src/quickActions/Membership__c.New_Membership_Contact_Role.quickAction b/src/quickActions/Membership__c.New_Membership_Contact_Role.quickAction new file mode 100644 index 00000000..7b405b80 --- /dev/null +++ b/src/quickActions/Membership__c.New_Membership_Contact_Role.quickAction @@ -0,0 +1,47 @@ + + + + Name + Membership__c.Name & " - " & TEXT(YEAR(TODAY())) + + + Start_Date__c + TODAY() + + true + + TwoColumnsLeftToRight + + + false + Contact__c + Required + + + false + Role__c + Edit + + + false + Is_Primary__c + Edit + + + false + Start_Date__c + Edit + + + false + End_Date__c + Edit + + + + + New + Membership_Contact_Role__c + Membership__c + Create + diff --git a/src/quickActions/Opportunity.New_Membership.quickAction b/src/quickActions/Opportunity.New_Membership.quickAction new file mode 100644 index 00000000..620c9c9e --- /dev/null +++ b/src/quickActions/Opportunity.New_Membership.quickAction @@ -0,0 +1,49 @@ + + + + Name + Opportunity.Account.Name & "- Membership" + + + true + + TwoColumnsLeftToRight + + + false + Account__c + Edit + + + false + Primary_Contact__c + Edit + + + false + Type__c + Edit + + + + + false + Does_Not_Expire__c + Edit + + + false + End_Date__c + Edit + + + false + Start_Date__c + Edit + + + + Membership__c + Opportunity__c + Create + diff --git a/src/reportTypes/Accounts_with_Memberships.reportType b/src/reportTypes/Accounts_with_Memberships.reportType new file mode 100644 index 00000000..6fc7ad47 --- /dev/null +++ b/src/reportTypes/Accounts_with_Memberships.reportType @@ -0,0 +1,346 @@ + + + Account + accounts + true + Accounts with related Membership records + + false + Memberships__r + + + + + false + Id + Account
+
+ + true + Name + Account
+
+ + false + Type + Account
+
+ + false + Parent + Account
+
+ + false + BillingAddress + Account
+
+ + false + BillingStreet + Account
+
+ + false + BillingCity + Account
+
+ + false + BillingState + Account
+
+ + false + BillingPostalCode + Account
+
+ + false + BillingCountry + Account
+
+ + false + BillingLatitude + Account
+
+ + false + BillingLongitude + Account
+
+ + false + BillingGeocodeAccuracy + Account
+
+ + false + ShippingAddress + Account
+
+ + false + ShippingStreet + Account
+
+ + false + ShippingCity + Account
+
+ + false + ShippingState + Account
+
+ + false + ShippingPostalCode + Account
+
+ + false + ShippingCountry + Account
+
+ + false + ShippingLatitude + Account
+
+ + false + ShippingLongitude + Account
+
+ + false + ShippingGeocodeAccuracy + Account
+
+ + false + Phone + Account
+
+ + false + Fax + Account
+
+ + false + AccountNumber + Account
+
+ + false + Website + Account
+
+ + false + Sic + Account
+
+ + false + Industry + Account
+
+ + false + AnnualRevenue + Account
+
+ + false + NumberOfEmployees + Account
+
+ + false + Ownership + Account
+
+ + false + TickerSymbol + Account
+
+ + false + Description + Account
+
+ + false + Rating + Account
+
+ + false + Site + Account
+
+ + false + Owner + Account
+
+ + false + CreatedDate + Account
+
+ + false + CreatedBy + Account
+
+ + false + LastModifiedDate + Account
+
+ + false + LastModifiedBy + Account
+
+ + false + LastActivityDate + Account
+
+ + false + Jigsaw + Account
+
+ + false + AccountSource + Account
+
+ + + + + false + First_Membership_Start_Date__c + Account
+
+ + false + Last_Membership_Start_Date__c + Account
+
+ + false + Membership_End_Date__c + Account
+
+ + false + Membership_Status__c + Account
+
+ + false + Membership_Type__c + Account
+
+ Accounts +
+ + + false + Id + Account.Memberships__r
+
+ + false + Owner + Account.Memberships__r
+
+ + true + Name + Account.Memberships__r
+
+ + false + CreatedDate + Account.Memberships__r
+
+ + false + CreatedBy + Account.Memberships__r
+
+ + false + LastModifiedDate + Account.Memberships__r
+
+ + false + LastModifiedBy + Account.Memberships__r
+
+ + false + LastActivityDate + Account.Memberships__r
+
+ + false + Does_Not_Expire__c + Account.Memberships__r
+
+ + false + End_Date__c + Account.Memberships__r
+
+ + false + Opportunity__c + Account.Memberships__r
+
+ + false + Origin__c + Account.Memberships__r
+
+ + false + Primary_Contact__c + Account.Memberships__r
+
+ + false + Product__c + Account.Memberships__r
+
+ + false + Start_Date__c + Account.Memberships__r
+
+ + false + Type__c + Account.Memberships__r
+
+ + false + Status__c + Account.Memberships__r
+
+ Memberships +
+
diff --git a/src/reports/Memberships-meta.xml b/src/reports/Memberships-meta.xml new file mode 100644 index 00000000..ec95120f --- /dev/null +++ b/src/reports/Memberships-meta.xml @@ -0,0 +1,4 @@ + + + Memberships + diff --git a/src/reports/Memberships/Accounts_w_Memberships_but_no_Contacts_ZBx.report b/src/reports/Memberships/Accounts_w_Memberships_but_no_Contacts_ZBx.report new file mode 100644 index 00000000..2541ddec --- /dev/null +++ b/src/reports/Memberships/Accounts_w_Memberships_but_no_Contacts_ZBx.report @@ -0,0 +1,84 @@ + + + + + + Account$Name + + + + + + Account.Memberships__r$Name + + + + + + Account.Memberships__r$Start_Date__c + + + + + + Account.Memberships__r$End_Date__c + + + + + + Account.Memberships__r$Status__c + + + + + + Account.Memberships__r$Type__c + + + + + + without + + Account$Id + + Contact + + Account + + + + A report from troubleshooting potential Account merges that have resulted in Memberships associated to an Account that no longer contains Contacts + + Tabular + + Accounts w/ Memberships but no Contacts + + + + co + + 1 + + + + Accounts_with_Memberships__c + + organization + + true + + true + + true + + + + Account$CreatedDate + + INTERVAL_CUSTOM + + + + diff --git a/src/tabs/Membership__c.tab b/src/tabs/Membership__c.tab new file mode 100644 index 00000000..178f1972 --- /dev/null +++ b/src/tabs/Membership__c.tab @@ -0,0 +1,6 @@ + + + true + Tab for managing memberships + Custom15: People + diff --git a/src/triggers/ContactTrigger.trigger b/src/triggers/ContactTrigger.trigger new file mode 100644 index 00000000..d5e1e7ea --- /dev/null +++ b/src/triggers/ContactTrigger.trigger @@ -0,0 +1,7 @@ +trigger ContactTrigger on Contact ( + before insert, after insert, + before update, after update, + before delete, after delete, + after undelete) { + new ContactHandler().run(); +} \ No newline at end of file diff --git a/src/triggers/ContactTrigger.trigger-meta.xml b/src/triggers/ContactTrigger.trigger-meta.xml new file mode 100644 index 00000000..394fe82d --- /dev/null +++ b/src/triggers/ContactTrigger.trigger-meta.xml @@ -0,0 +1,5 @@ + + + 49.0 + Active + \ No newline at end of file diff --git a/src/triggers/MembershipTrigger.trigger b/src/triggers/MembershipTrigger.trigger new file mode 100644 index 00000000..fcea7a40 --- /dev/null +++ b/src/triggers/MembershipTrigger.trigger @@ -0,0 +1,8 @@ +/** + * Created by ChrisPifer on 9/24/2020. + */ + +trigger MembershipTrigger on Membership__c (before insert, before update, before delete, + after insert, after update, after delete, after undelete) { + new MembershipHandler().run(); +} \ No newline at end of file diff --git a/src/triggers/MembershipTrigger.trigger-meta.xml b/src/triggers/MembershipTrigger.trigger-meta.xml new file mode 100644 index 00000000..9b3a8e38 --- /dev/null +++ b/src/triggers/MembershipTrigger.trigger-meta.xml @@ -0,0 +1,5 @@ + + + 48.0 + Active + From bfc14d8d07f6997e175d7db44c0d52d4d83af7da Mon Sep 17 00:00:00 2001 From: Allison Letts Date: Tue, 30 Mar 2021 15:07:19 -0400 Subject: [PATCH 2/2] fully deployable --- cumulusci.yml | 8 ++++- ...rship__c-Membership Layout.layout-meta.xml | 32 ++----------------- .../Membership__c-Membership Layout.layout | 32 ++----------------- 3 files changed, 11 insertions(+), 61 deletions(-) diff --git a/cumulusci.yml b/cumulusci.yml index c1784e5d..ea3762c4 100644 --- a/cumulusci.yml +++ b/cumulusci.yml @@ -16,4 +16,10 @@ tasks: robot_testdoc: options: path: robot/Membership-Management/tests - output: robot/Membership-Management/doc/Membership-Management_tests.html \ No newline at end of file + output: robot/Membership-Management/doc/Membership-Management_tests.html + +flows: + deploy_unmanaged: + steps: + 4: + task: None \ No newline at end of file diff --git a/force-app/main/default/layouts/Membership__c-Membership Layout.layout-meta.xml b/force-app/main/default/layouts/Membership__c-Membership Layout.layout-meta.xml index 78924719..6354b5ae 100644 --- a/force-app/main/default/layouts/Membership__c-Membership Layout.layout-meta.xml +++ b/force-app/main/default/layouts/Membership__c-Membership Layout.layout-meta.xml @@ -93,16 +93,7 @@ Record - - FeedItem.TextPost - QuickAction - 0 - - - FeedItem.ContentPost - QuickAction - 1 - + Membership__c.New_Membership_Contact_Role QuickAction @@ -143,26 +134,7 @@ QuickAction 9 - - FeedItem.RypplePost - QuickAction - 10 - - - FeedItem.LinkPost - QuickAction - 11 - - - FeedItem.PollPost - QuickAction - 12 - - - FeedItem.QuestionPost - QuickAction - 13 - + Share StandardButton diff --git a/src/layouts/Membership__c-Membership Layout.layout b/src/layouts/Membership__c-Membership Layout.layout index 78924719..6354b5ae 100644 --- a/src/layouts/Membership__c-Membership Layout.layout +++ b/src/layouts/Membership__c-Membership Layout.layout @@ -93,16 +93,7 @@ Record - - FeedItem.TextPost - QuickAction - 0 - - - FeedItem.ContentPost - QuickAction - 1 - + Membership__c.New_Membership_Contact_Role QuickAction @@ -143,26 +134,7 @@ QuickAction 9 - - FeedItem.RypplePost - QuickAction - 10 - - - FeedItem.LinkPost - QuickAction - 11 - - - FeedItem.PollPost - QuickAction - 12 - - - FeedItem.QuestionPost - QuickAction - 13 - + Share StandardButton