Skip to content

Commit

Permalink
Merge pull request #61 from Sundae-Shop-Consulting/feature/hours-star…
Browse files Browse the repository at this point in the history
…t-date

Add calculations and validation for Volunteer Hours Logs
  • Loading branch information
lmeerkatz authored Aug 16, 2024
2 parents a909a6f + 5d3fd8f commit b644a60
Show file tree
Hide file tree
Showing 17 changed files with 662 additions and 7 deletions.
5 changes: 4 additions & 1 deletion .forceignore
Original file line number Diff line number Diff line change
Expand Up @@ -111,4 +111,7 @@ package.xml
**/.eslintrc.json

# LWC Jest
**/__tests__/**
**/__tests__/**
**/tsconfig.json

**/*.ts
151 changes: 151 additions & 0 deletions force-app/volunteers/classes/TestDataFactoryBase.cls
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 force-app/volunteers/classes/TestDataFactoryBase.cls-meta.xml
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>
17 changes: 17 additions & 0 deletions force-app/volunteers/classes/TestDataFactoryScenarios.cls
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;
}

}
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 force-app/volunteers/classes/VolunteerHoursCalculations.cls
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 {}
}
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>
Loading

0 comments on commit b644a60

Please sign in to comment.