-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #61 from Sundae-Shop-Consulting/feature/hours-star…
…t-date Add calculations and validation for Volunteer Hours Logs
- Loading branch information
Showing
17 changed files
with
662 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -111,4 +111,7 @@ package.xml | |
**/.eslintrc.json | ||
|
||
# LWC Jest | ||
**/__tests__/** | ||
**/__tests__/** | ||
**/tsconfig.json | ||
|
||
**/*.ts |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
@IsTest | ||
public with sharing class TestDataFactoryBase { | ||
public static List<Account> generateAccounts(Integer count) { | ||
List<Account> accts = new List<Account>(); | ||
for (Integer i = 0; i < count; i++) { | ||
accts.add(new Account( | ||
Name = 'Test Account ' + (i + 1) | ||
)); | ||
} | ||
return accts; | ||
} | ||
|
||
public static List<Account> insertAccounts(Integer count) { | ||
List<Account> accts = generateAccounts(count); | ||
insert accts; | ||
return accts; | ||
} | ||
|
||
public static List<Contact> generateContacts(Integer count) { | ||
List<Account> accts = insertAccounts(count); | ||
List<Contact> cons = new List<Contact>(); | ||
for (Integer i = 0; i < count; i++) { | ||
cons.add(new Contact( | ||
FirstName = 'Test', | ||
LastName = 'Contact ' + (i + 1), | ||
AccountId = accts[i].Id | ||
)); | ||
} | ||
return cons; | ||
} | ||
|
||
public static List<Contact> insertContacts(Integer count) { | ||
List<Contact> cons = generateContacts(count); | ||
insert cons; | ||
return cons; | ||
} | ||
|
||
public static List<VolunteerPosition__c> generatePositions(Integer count) { | ||
List<VolunteerPosition__c> positions = new List<VolunteerPosition__c>(); | ||
for (Integer i = 0; i < count; i++) { | ||
positions.add(new VolunteerPosition__c( | ||
Name = 'Test Position ' + (i + 1) | ||
)); | ||
} | ||
return positions; | ||
} | ||
|
||
public static List<VolunteerPosition__c> insertPositions(Integer count) { | ||
List<VolunteerPosition__c> positions = generatePositions(count); | ||
insert positions; | ||
return positions; | ||
} | ||
|
||
public static List<VolunteerActivity__c> generateActivities(Integer count, Id volunteerPositionId) { | ||
List<VolunteerActivity__c> activities = new List<VolunteerActivity__c>(); | ||
for (Integer i = 0; i < count; i++) { | ||
activities.add(new VolunteerActivity__c( | ||
Name = 'Test Activity ' + (i + 1), | ||
VolunteerPosition__c = volunteerPositionId | ||
)); | ||
} | ||
return activities; | ||
} | ||
|
||
public static List<VolunteerActivity__c> insertActivities(Integer count, Id volunteerPositionId) { | ||
List<VolunteerActivity__c> activities = generateActivities(count, volunteerPositionId); | ||
insert activities; | ||
return activities; | ||
} | ||
|
||
public static VolunteerPositionAssignment__c generatePositionAssignment(Id volunteerContactId, Id volunteerPositionId, String status) { | ||
return new VolunteerPositionAssignment__c( | ||
VolunteerContact__c = volunteerContactId, | ||
VolunteerPosition__c = volunteerPositionId, | ||
Status__c = status | ||
); | ||
} | ||
|
||
public static List<VolunteerPositionAssignment__c> generatePositionAssignments(List<Id> volunteerContactIds, List<Id> volunteerPositionIds, String status) { | ||
List<VolunteerPositionAssignment__c> positionAssignments = new List<VolunteerPositionAssignment__c>(); | ||
for (Id contactId : volunteerContactIds) { | ||
for (Id volunteerPositionId : volunteerPositionIds) { | ||
positionAssignments.add( | ||
generatePositionAssignment(contactId, volunteerPositionId, status) | ||
); | ||
} | ||
} | ||
return positionAssignments; | ||
} | ||
|
||
public static List<VolunteerPositionAssignment__c> insertPositionAssignments(List<Id> volunteerContactIds, List<Id> volunteerPositionIds, String status) { | ||
List<VolunteerPositionAssignment__c> positionAssignments = generatePositionAssignments(volunteerContactIds, volunteerPositionIds, status); | ||
insert positionAssignments; | ||
return positionAssignments; | ||
} | ||
|
||
public static VolunteerActivityAssignment__c generateActivityAssignment(Id volunteerContactId, Id volunteerActivityId, String status) { | ||
return new VolunteerActivityAssignment__c( | ||
VolunteerContact__c = volunteerContactId, | ||
VolunteerActivity__c = volunteerActivityId, | ||
Status__c = status | ||
); | ||
} | ||
|
||
public static List<VolunteerActivityAssignment__c> generateActivityAssignments(List<Id> volunteerContactIds, List<Id> volunteerActivityIds, String status) { | ||
List<VolunteerActivityAssignment__c> activityAssignments = new List<VolunteerActivityAssignment__c>(); | ||
for (Id contactId : volunteerContactIds) { | ||
for (Id volunteerActivityId : volunteerActivityIds) { | ||
activityAssignments.add( | ||
generateActivityAssignment(contactId, volunteerActivityId, status) | ||
); | ||
} | ||
} | ||
return activityAssignments; | ||
} | ||
|
||
public static List<VolunteerActivityAssignment__c> insertActivityAssignments(List<Id> volunteerContactIds, List<Id> volunteerActivityIds, String status) { | ||
List<VolunteerActivityAssignment__c> activityAssignments = generateActivityAssignments(volunteerContactIds, volunteerActivityIds, status); | ||
insert activityAssignments; | ||
return activityAssignments; | ||
} | ||
|
||
public static VolunteerHoursLog__c generateVolunteerHoursLog(Id volunteerContactId, Id volunteerActivityId, DateTime startDateTime, DateTime endDateTime) { | ||
return new VolunteerHoursLog__c( | ||
VolunteerContact__c = volunteerContactId, | ||
VolunteerActivity__c = volunteerActivityId, | ||
StartDateTime__c = startDateTime, | ||
EndDateTime__c = endDateTime | ||
); | ||
} | ||
|
||
public static VolunteerHoursLog__c generateVolunteerHoursLog(Id volunteerContactId, Id volunteerActivityId, DateTime startDateTime, Decimal hours) { | ||
return new VolunteerHoursLog__c( | ||
VolunteerContact__c = volunteerContactId, | ||
VolunteerActivity__c = volunteerActivityId, | ||
StartDateTime__c = startDateTime, | ||
Hours__c = hours | ||
); | ||
} | ||
|
||
public static VolunteerHoursLog__c queryLog(Id logId) { | ||
List<VolunteerHoursLog__c> logs = [SELECT Id, VolunteerActivity__c, VolunteerContact__c, StartDateTime__c, EndDateTime__c, Hours__c FROM VolunteerHoursLog__c WHERE Id = :logId LIMIT 1]; | ||
if (logs.isEmpty()) { | ||
throw new TestDataException('No VolunteerHoursLog record found with Id: ' + logId); | ||
} | ||
return logs[0]; | ||
} | ||
|
||
public class TestDataException extends Exception {} | ||
|
||
} |
5 changes: 5 additions & 0 deletions
5
force-app/volunteers/classes/TestDataFactoryBase.cls-meta.xml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata"> | ||
<apiVersion>61.0</apiVersion> | ||
<status>Active</status> | ||
</ApexClass> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
@IsTest | ||
public with sharing class TestDataFactoryScenarios { | ||
public static VolunteerPositionAssignment__c createActiveVolunteer() { | ||
Contact volunteerContact = TestDataFactoryBase.insertContacts(1)[0]; | ||
VolunteerPosition__c position = TestDataFactoryBase.insertPositions(1)[0]; | ||
VolunteerPositionAssignment__c positionAssignment = TestDataFactoryBase.insertPositionAssignments(new List<Id>{volunteerContact.Id}, new List<Id>{position.Id}, 'Active')[0]; | ||
return positionAssignment; | ||
} | ||
|
||
public static VolunteerActivityAssignment__c assignVolunteerToActivity() { | ||
VolunteerPositionAssignment__c positionAssignment = createActiveVolunteer(); | ||
VolunteerActivity__c activity = TestDataFactoryBase.insertActivities(1, positionAssignment.VolunteerPosition__c)[0]; | ||
VolunteerActivityAssignment__c activityAssignment = TestDataFactoryBase.insertActivityAssignments(new List<Id>{positionAssignment.VolunteerContact__c}, new List<Id>{activity.Id}, 'Open')[0]; | ||
return activityAssignment; | ||
} | ||
|
||
} |
5 changes: 5 additions & 0 deletions
5
force-app/volunteers/classes/TestDataFactoryScenarios.cls-meta.xml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata"> | ||
<apiVersion>61.0</apiVersion> | ||
<status>Active</status> | ||
</ApexClass> |
160 changes: 160 additions & 0 deletions
160
force-app/volunteers/classes/VolunteerHoursCalculations.cls
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
public with sharing class VolunteerHoursCalculations { | ||
|
||
|
||
public void calculateInsertedLog(VolunteerHoursLog__c log) { | ||
if (isValidInsert(log)) { | ||
populateLog(log); | ||
} else { | ||
throw new VolunteerHoursValidationException('Start Date/Time, End Date/Time and Hours are missing or have conflicting values'); | ||
} | ||
} | ||
|
||
public void calculateUpdatedLog(VolunteerHoursLog__c oldLog, VolunteerHoursLog__c newLog) { | ||
if (isValidUpdate(oldLog, newLog)) { | ||
updateLog(oldLog, newLog); | ||
} else { | ||
throw new VolunteerHoursValidationException('Start Date/Time, End Date/Time and Hours are missing or have conflicting values'); | ||
} | ||
} | ||
|
||
private Integer countPopulatedRelevantFields(VolunteerHoursLog__c log) { | ||
Integer countOfPopulatedFields = 0; | ||
if (log.StartDateTime__c != null) { | ||
countOfPopulatedFields++; | ||
} | ||
if (log.EndDateTime__c != null) { | ||
countOfPopulatedFields++; | ||
} | ||
if (log.Hours__c != null) { | ||
countOfPopulatedFields++; | ||
} | ||
return countOfPopulatedFields; | ||
} | ||
|
||
private Integer countUpdatedRelevantFields(VolunteerHoursLog__c oldLog, VolunteerHoursLog__c newLog) { | ||
Integer countOfUpdatedFields = 0; | ||
if (oldLog.StartDateTime__c != newLog.StartDateTime__c) { | ||
countOfUpdatedFields++; | ||
} | ||
if (oldLog.EndDateTime__c != newLog.EndDateTime__c) { | ||
countOfUpdatedFields++; | ||
} | ||
if (oldLog.Hours__c != newLog.Hours__c) { | ||
countOfUpdatedFields++; | ||
} | ||
return countOfUpdatedFields; | ||
} | ||
|
||
private Boolean dateTimesAndHoursAddUp(VolunteerHoursLog__c log) { | ||
Integer minutes = log.Hours__c.intValue() * 60; | ||
return (log.StartDateTime__c.addMinutes(minutes) == log.EndDateTime__c); | ||
} | ||
|
||
@TestVisible | ||
private Boolean isValidInsert(VolunteerHoursLog__c log) { | ||
Integer countOfPopulatedFields = countPopulatedRelevantFields(log); | ||
if (countOfPopulatedFields == 2) { | ||
return true; | ||
} else if (countOfPopulatedFields == 3 && dateTimesAndHoursAddUp(log)) { | ||
return true; | ||
} else { | ||
return false; | ||
} | ||
} | ||
|
||
@TestVisible | ||
private Boolean isValidUpdate(VolunteerHoursLog__c oldLog, VolunteerHoursLog__c newLog) { | ||
Integer countOfUpdatedFields = countUpdatedRelevantFields(oldLog, newLog); | ||
if (countOfUpdatedFields < 3) { | ||
return true; | ||
} else if (countPopulatedRelevantFields(newLog) == 3 && dateTimesAndHoursAddUp(newLog)) { | ||
return true; | ||
} | ||
return false; | ||
} | ||
|
||
private void populateLog(VolunteerHoursLog__c log) { | ||
if (log.StartDateTime__c == null) { | ||
log.StartDateTime__c = calculateStartDateTime(log.EndDateTime__c, log.Hours__c); | ||
} else if (log.EndDateTime__c == null) { | ||
log.EndDateTime__c = calculateEndDateTime(log.StartDateTime__c, log.Hours__c); | ||
} else if (log.Hours__c == null) { | ||
log.Hours__c = calculateHours(log.StartDateTime__c, log.EndDateTime__c); | ||
} | ||
} | ||
|
||
private void updateLog(VolunteerHoursLog__c oldLog, VolunteerHoursLog__c newLog) { | ||
Integer countOfUpdatedFields = countUpdatedRelevantFields(oldLog, newLog); | ||
if (countOfUpdatedFields == 1) { | ||
if (isStartDateTimeChanged(oldLog, newLog)) { | ||
updateHours(newLog); | ||
} else if (isEndDateTimeChanged(oldLog, newLog)) { | ||
updateHours(newLog); | ||
} else if (isHoursChanged(oldLog, newLog)) { | ||
updateEndDateTime(newLog); | ||
} | ||
} else if (countOfUpdatedFields == 2) { | ||
if (isStartDateTimeChanged(oldLog, newLog) && isEndDateTimeChanged(oldLog, newLog)) { | ||
updateHours(newLog); | ||
} else if (isEndDateTimeChanged(oldLog, newLog) && isHoursChanged(oldLog, newLog)) { | ||
updateStartDateTime(newLog); | ||
} else if (isHoursChanged(oldLog, newLog) && isStartDateTimeChanged(oldLog, newLog)) { | ||
updateEndDateTime(newLog); | ||
} | ||
} | ||
} | ||
|
||
private void updateHours(VolunteerHoursLog__c log) { | ||
log.Hours__c = calculateHours(log.StartDateTime__c, log.EndDateTime__c); | ||
} | ||
|
||
private void updateEndDateTime(VolunteerHoursLog__c log) { | ||
log.EndDateTime__c = calculateEndDateTime(log.StartDateTime__c, log.Hours__c); | ||
} | ||
|
||
private void updateStartDateTime(VolunteerHoursLog__c log) { | ||
log.StartDateTime__c = calculateStartDateTime(log.EndDateTime__c, log.Hours__c); | ||
} | ||
|
||
|
||
private Boolean isStartDateTimeChanged(VolunteerHoursLog__c oldLog, VolunteerHoursLog__c newLog) { | ||
return oldLog.StartDateTime__c != newLog.StartDateTime__c; | ||
} | ||
|
||
private Boolean isEndDateTimeChanged(VolunteerHoursLog__c oldLog, VolunteerHoursLog__c newLog) { | ||
return oldLog.EndDateTime__c != newLog.EndDateTime__c; | ||
} | ||
|
||
private Boolean isHoursChanged(VolunteerHoursLog__c oldLog, VolunteerHoursLog__c newLog) { | ||
return oldLog.Hours__c != newLog.Hours__c; | ||
} | ||
|
||
@TestVisible | ||
private DateTime calculateStartDateTime(DateTime endDateTime, Decimal hours) { | ||
// addHours doesn't support decimal values. sigh. | ||
Integer minutes = Math.round(hours * 60); | ||
return endDateTime.addMinutes(0 - minutes); | ||
} | ||
|
||
@TestVisible | ||
private DateTime calculateEndDateTime(DateTime startDateTime, Decimal hours) { | ||
Integer minutes = Math.round(hours * 60); | ||
return startDateTime.addMinutes(minutes); | ||
} | ||
|
||
@TestVisible | ||
private Decimal calculateHours(DateTime startDateTime, DateTime endDateTime) { | ||
return hoursBetween(endDateTime, startDateTime); | ||
} | ||
|
||
private Decimal hoursBetween(DateTime endDateTime, DateTime startDateTime) { | ||
Long startMilliseconds = startDateTime.getTime(); | ||
Long endMilliseconds = endDateTime.getTime(); | ||
|
||
Long milliseconds = endMilliseconds - startMilliseconds; | ||
Decimal hours = milliseconds / (1000.0 * 60 * 60); | ||
return hours; | ||
} | ||
|
||
public class VolunteerHoursValidationException extends Exception {} | ||
} |
5 changes: 5 additions & 0 deletions
5
force-app/volunteers/classes/VolunteerHoursCalculations.cls-meta.xml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata"> | ||
<apiVersion>61.0</apiVersion> | ||
<status>Active</status> | ||
</ApexClass> |
Oops, something went wrong.