diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
new file mode 100644
index 0000000..7342c09
--- /dev/null
+++ b/.github/workflows/main.yml
@@ -0,0 +1,56 @@
+# Unique name for this workflow
+name: Run Unit Tests
+
+# Workflow starts when receiving custom event sent by CI workflow
+on: [push, pull_request]
+
+# Jobs to be executed
+jobs:
+ deploy-and-test:
+ runs-on: ubuntu-latest
+
+ steps:
+ # Setup Node (for LWC tests)
+ - name: Setup NPM
+ uses: actions/setup-node@v1
+ with:
+ node-version: 16
+
+ # Install Salesforce CLI
+ - name: "Install Salesforce CLI"
+ run: npm install sfdx-cli --global
+
+ # Checkout the source code
+ - name: "Checkout source code"
+ uses: actions/checkout@v2
+
+ # Store secret for dev hub
+ - name: "Populate auth file with DEVHUB_SFDX_URL secret"
+ shell: bash
+ run: echo ${{ secrets.DEVHUB_SFDX_URL}} > ./DEVHUB_SFDX_URL.txt
+
+ # Authenticate dev hub
+ - name: "Authenticate Dev Hub"
+ run: sfdx auth:sfdxurl:store -f ./DEVHUB_SFDX_URL.txt -a devhub -d
+ #node_modules/sfdx-cli/bin/run force:auth:jwt:grant --clientid ${{ secrets.SALESFORCE_CONSUMER_KEY }} --jwtkeyfile server/server.key --username ${{ secrets.SALESFORCE_DEVHUB_USERNAME}} --setdefaultdevhubusername --setalias devhub
+
+ # Remove auth file
+ - name: "Remove auth file"
+ run: rm -f ./DEVHUB_SFDX_URL.txt
+
+ # Create scratch org
+ - name: "Create scratch org"
+ run: sfdx force:org:create -f config/project-scratch-def.json -a scratch-org -s -d 1
+
+ # Push source to scratch org
+ - name: "Push source to scratch org"
+ run: sfdx force:source:push
+
+ # Run Apex tests
+ - name: "Run apex tests"
+ run: sfdx force:apex:test:run -w 5
+
+ # Housekeeping
+ - name: "Delete scratch org"
+ if: always()
+ run: sfdx force:org:delete -p -u scratch-org
\ No newline at end of file
diff --git a/config/project-scratch-def.json b/config/project-scratch-def.json
new file mode 100644
index 0000000..62156e0
--- /dev/null
+++ b/config/project-scratch-def.json
@@ -0,0 +1,20 @@
+{
+ "orgName": "salonitinker",
+ "edition": "Developer",
+ "features": [],
+ "settings": {
+ "lightningExperienceSettings": {
+ "enableS1DesktopEnabled": true
+ },
+ "mobileSettings": {
+ "enableS1EncryptedStoragePref2": false
+ },
+ "userManagementSettings": {
+ "enableNewProfileUI": true
+ },
+ "contentSettings": {
+ "skipContentAssetTriggers": true,
+ "skipContentAssetTriggersOnDeploy": true
+ }
+ }
+}
\ No newline at end of file
diff --git a/force-app/main/default/classes/AccountTrigger.trigger b/force-app/main/default/classes/AccountTrigger.trigger
new file mode 100644
index 0000000..35df7a3
--- /dev/null
+++ b/force-app/main/default/classes/AccountTrigger.trigger
@@ -0,0 +1,17 @@
+trigger AccountTrigger on Account (before insert, before update, before delete, after insert, after update, after delete, after undelete) {
+
+ if (Trigger.isAfter) {
+
+ if (Trigger.isInsert) {}
+
+ if (Trigger.isUpdate) {
+ //AccountTriggerEmailTesting.method(Trigger.new, Trigger.Old, Trigger.newMap, Trigger.oldMap);
+ AccountTriggerHandler.onAfterUpdate (Trigger.new, Trigger.Old, Trigger.newMap, Trigger.oldMap);
+ }
+
+ if (Trigger.isDelete) {}
+
+ if (Trigger.isUndelete) {}
+
+ }
+}
\ No newline at end of file
diff --git a/force-app/main/default/classes/AccountTrigger.trigger-meta.xml b/force-app/main/default/classes/AccountTrigger.trigger-meta.xml
new file mode 100644
index 0000000..7acf013
--- /dev/null
+++ b/force-app/main/default/classes/AccountTrigger.trigger-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 50.0
+ Active
+
diff --git a/force-app/main/default/classes/AccountTriggerHandler.cls b/force-app/main/default/classes/AccountTriggerHandler.cls
new file mode 100644
index 0000000..5d2ab00
--- /dev/null
+++ b/force-app/main/default/classes/AccountTriggerHandler.cls
@@ -0,0 +1,38 @@
+public with sharing class AccountTriggerHandler {
+ public static void onAfterUpdate (List lstNewAccount, List lstOldAccount, Map mapNewAccount, Map mapOldAccount) {
+ sendEmailToOldOwnerOnAccountOwnerChange (lstNewAccount, mapOldAccount);
+ }
+
+ public static void sendEmailToOldOwnerOnAccountOwnerChange (List lstNewAccount, Map mapOldAccount){
+
+ try {
+ Map > mapOldOwnerIdWithAccountNames = new Map > ();
+
+ for (Account objAccount : [ SELECT Id, Name, OwnerId FROM Account WHERE ID IN : lstNewAccount ]) { // => Storing account Names and Old Owner Id if Account Owner has been changed
+ Account objOldAccount = (Account) mapOldAccount.get(objAccount.ID);
+ if (objAccount.OwnerId != objOldAccount.OwnerId) {
+ if (!mapOldOwnerIdWithAccountNames.containsKey(objOldAccount.OwnerId))
+ mapOldOwnerIdWithAccountNames.put(objOldAccount.OwnerId, new List ());
+ mapOldOwnerIdWithAccountNames.get(objOldAccount.OwnerId).add(objAccount.Name);
+ }
+ }
+
+ if (mapOldOwnerIdWithAccountNames.size() > 0) { // => Sending Email to the Old Owner if Account Owner has been changed
+ for (User objUsers : [ SELECT ID, Name, Email FROM User WHERE ID IN : mapOldOwnerIdWithAccountNames.keySet() ]) {
+ Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
+ mail.setSubject('Account Owner Changed!');
+ String [] toAddresses = new String [] { objUsers.Email };
+ mail.setToAddresses(toAddresses);
+ List lstChangedOwnersAccountNames = new List ();
+ lstChangedOwnersAccountNames.addAll(mapOldOwnerIdWithAccountNames.get(objUsers.Id));
+ mail.setPlainTextBody('Dear '+ objUsers.Name + ',\n\nOwner of the following Account(s) has been changed on ' + System.now().format('dd/MM/yyyy HH:mm:ss') + ':\n' + lstChangedOwnersAccountNames + '\n\nThank you!');//mapOldOwnerIdWithAccountName.values()
+ Messaging.sendEmail(new Messaging.Email[] { mail });
+ }
+ }
+ }
+
+ catch (Exception e) {
+ System.debug ('--- Exception in sendEmailToOldOwnerOnAccountOwnerChange --- ' + e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/force-app/main/default/classes/AccountTriggerHandler.cls-meta.xml b/force-app/main/default/classes/AccountTriggerHandler.cls-meta.xml
new file mode 100644
index 0000000..541584f
--- /dev/null
+++ b/force-app/main/default/classes/AccountTriggerHandler.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 50.0
+ Active
+
diff --git a/force-app/main/default/classes/AccountTriggerHandlerTest.cls b/force-app/main/default/classes/AccountTriggerHandlerTest.cls
new file mode 100644
index 0000000..86abe66
--- /dev/null
+++ b/force-app/main/default/classes/AccountTriggerHandlerTest.cls
@@ -0,0 +1,55 @@
+@isTest
+public with sharing class AccountTriggerHandlerTest {
+ @TestSetup
+ static void makeData() {
+ Profile objProfile = [SELECT Id FROM Profile WHERE Name='Standard User'];
+
+ User objUser1 = new User(
+ Alias = 'Test',
+ LastName='User 1',
+ Email='TestUser1@gmail.com',
+ EmailEncodingKey='UTF-8',
+ LanguageLocaleKey='en_US',
+ LocaleSidKey='en_US',
+ ProfileId = objProfile.Id,
+ TimeZoneSidKey='America/Los_Angeles',
+ UserName='testuserforownerchange1@gmail.com'
+ );
+ insert objUser1;
+
+ User objUser2 = new User(
+ Alias = 'Test',
+ LastName='User 2',
+ Email='testuser2@gmail.com',
+ EmailEncodingKey='UTF-8',
+ LanguageLocaleKey='en_US',
+ LocaleSidKey='en_US',
+ ProfileId = objProfile.Id,
+ TimeZoneSidKey='America/Los_Angeles',
+ UserName='testuserforownerchange2@gmail.com'
+ );
+ insert objUser2;
+ }
+ @isTest
+ static void changeAccountOwner() {
+
+ Test.startTest();
+
+ List lstUser = new List ();
+
+ for (User objUser : [ SELECT Id FROM User LIMIT 2 ])
+ lstUser.add(objUser);
+
+ Account objAccount = new Account(
+ Name = 'Test Account',
+ OwnerId = lstUser[0].Id
+ );
+ insert objAccount;
+
+ objAccount.OwnerId = lstUser[1].Id;
+
+ update objAccount;
+
+ Test.stopTest();
+ }
+}
\ No newline at end of file
diff --git a/force-app/main/default/classes/AccountTriggerHandlerTest.cls-meta.xml b/force-app/main/default/classes/AccountTriggerHandlerTest.cls-meta.xml
new file mode 100644
index 0000000..541584f
--- /dev/null
+++ b/force-app/main/default/classes/AccountTriggerHandlerTest.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 50.0
+ Active
+
diff --git a/server/server.key b/server/server.key
new file mode 100644
index 0000000..2854b07
--- /dev/null
+++ b/server/server.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEA8HD1wvg8/p9LKngDcfssb8+bJIpQPpIpWpnvfkeQUzTn/HT7
+pkFPqwQRclxyAp9lkchW+cdp/PXTPlwSVBMezL2glyrCFK3SKnXPvk2OVnhcKpF/
+9rKB6u4PWRQ1yXpCP7b+RawUILRm7Kq+feaiXnwJSRLG4e5hBuqSLJii/ahuWnjt
+NfMKIrmyrIddOqxW3mZbwIpaqaZ1h/n2wJ2VFWt+pk59TngcJXeRIQOBmxjPzPTD
+RLui1RiVzLBhVOHITg+Ba7bWeKUNxDkeClcnf7Um9mkgdA3urQYQdhghMomnrfZi
+VFTHkemx8/rooYO+79WA0x43JwwH/UbDyGsoCwIDAQABAoIBAQCN+YWEzOPnTdQq
+jZcrNK82jpYDa5Cagd/dWcwq+sLj/veFAOafcy1BxDMxHhlMJybogC2ra+7PqbBE
+wEUqU+f56XbsDBlx8HmLDOnQaydDM+roT+YJm7pPKKbzaJWiFIkN6lYWedegwCWO
+VfwJh6gUtpUdCvNY8VNlrBkIMFD+CdOwXf2G6Q+Ax3j/kzT9iX7lHqaiADKN+ATe
+HlAAA5NyvNkGkKXori1/va7CXM4VLbjtyHvMvvTAqZSLzTvFF/Zf/TXH7w4IY4bg
+OQO2CKdOWfOiHU03QyFf0zxM8TAHhEv4+qafL5CujoJdmiOmkr4ZcoKPZdTDPoqb
+J4sZzn7BAoGBAPhsA8IavQigN8LwI3aAPaP6Zsv9BErGbWsJu1Cco0iszJZJfYdW
+S6cAihovwrwPlxgxxDBg+wJ0GgvyekcWZ5kOnIxaGZWktSUKuM1akEGa7b05mzta
+GwDOSeVlCYbp6jekeDYjZ2S+UFv60e58dUftqk3oTAnvD+cNV0syc1exAoGBAPfG
+n08I6LkPTUDixWGLJJCGypMNy4u5tuYRd7mqtVSi1WtnI2lAqfL+q4Gor4yMIT0R
+L0RHNy0unBgqWdQAFVFKaCLaQG1aTCGyjkzosVzlk+SY0u1QWeLNi1wIOOvObBsn
+ap7PNFRngoUjzJRDk7g4m98CUOl73N2YX7tCZ+Z7AoGALCf+7wSTrzHTV4oZs5mN
+21TJ6t5ZYfUtQ8ZiDi0ZdPSKFPiRIHo9TjzxT0BIC+d1gu5toe/LDh+W9aKvC223
+EmSffO8S9x9kt2XVfWLom1Cvi1/u8LjObfE8/1JbRCjV4fgRCgZ2/Vg6rm7B2/Mx
+YYykJd7xjR56JCah6pLYH7ECgYBC5z7c+GrejWMuwgzJ6ehVng/U5USw1dStCspp
+N87+q3k1JwRIx00cb+efewzrxmqzMpCwvwoHCS7c15Kpy9v1t0GnJ4d9/4PPFYeW
+/L3nd5YXg6xwp+GdfCLFFvNHuNU3/8yqVQisQcbcbG4xGo4okCCGdvmiEe/xZdNz
+nNqxQQKBgBozQM9V77lokVyisEC58VMyKttn8mhOuFeHQBNQ2v6LISJSQnSMRrm+
+I0SVX0TKf/WC0cf/HC7zfJOLVNKLzggmWU4DtGyyp33kDPIrW/izFxoDFHBOP4T8
+XO2gFkFQI2CWjEj1t/62cHhSACXStInB18orK3qYX5ju2+Ph/WNE
+-----END RSA PRIVATE KEY-----
diff --git a/sfdx-project.json b/sfdx-project.json
new file mode 100644
index 0000000..1ac36e6
--- /dev/null
+++ b/sfdx-project.json
@@ -0,0 +1,11 @@
+{
+ "packageDirectories": [
+ {
+ "path": "force-app",
+ "default": true
+ }
+ ],
+ "namespace": "",
+ "sfdcLoginUrl": "https://login.salesforce.com",
+ "sourceApiVersion": "51.0"
+}