diff --git a/src/classes/DContractTriggerController.cls b/src/classes/DContractTriggerController.cls new file mode 100644 index 00000000..1e1a77ae --- /dev/null +++ b/src/classes/DContractTriggerController.cls @@ -0,0 +1,38 @@ +public class DContractTriggerController { + + public static void handleBeforeUpdate(List updated, Map beforeUpdate) { + for (DContract__c upsertedContract : updated) { + DContract__c oldValue = beforeUpdate.get(upsertedContract.Id); + if ((oldValue == null || oldValue.FTE_Tracker__c != upsertedContract.FTE_Tracker__c)) { + upsertedContract.addError('FTE Tracker is currently calculating Empolyee Work Cards, try update FTE Tracker field later.'); + } + } + } + + public static void handleAfterUpdate(List updated, Map beforeUpdate) { + Set contracts = new Set(); + List contractsToUpdate = new List(); + Boolean fteJob = FTETrackerHelper.loadWorkCardJobStatus().isRunning; + + for (DContract__c upsertedContract : updated) { + DContract__c oldValue = beforeUpdate.get(upsertedContract.Id); + if ((oldValue == null || oldValue.FTE_Tracker__c != upsertedContract.FTE_Tracker__c) && (upsertedContract.Skip_FTE_Tracker_Trigger__c == false)) { + contracts.add(upsertedContract.Id); + } else if (upsertedContract.Skip_FTE_Tracker_Trigger__c == true) { // we want skip this batch job FTE Contract is uploaded by CSV File, we want have full controll and run moving hours batch job after Generating work cards + contractsToUpdate.add(new DContract__c(Id = upsertedContract.Id, Skip_FTE_Tracker_Trigger__c = false)); + } + } + + if (contractsToUpdate.size() > 0) { + update contractsToUpdate; + } + + FTETrackerHelper.markTemplates(contracts); + + if (contracts.size() > 0) { + if (!Test.isRunningTest()) { + Database.executeBatch(new FTETimeAllocator(false), 1); + } + } + } +} \ No newline at end of file diff --git a/src/classes/DContractTriggerController.cls-meta.xml b/src/classes/DContractTriggerController.cls-meta.xml new file mode 100644 index 00000000..cbddff8c --- /dev/null +++ b/src/classes/DContractTriggerController.cls-meta.xml @@ -0,0 +1,5 @@ + + + 38.0 + Active + diff --git a/src/classes/FTEController.cls b/src/classes/FTEController.cls index 9f681e01..ab90407d 100644 --- a/src/classes/FTEController.cls +++ b/src/classes/FTEController.cls @@ -3,7 +3,6 @@ public virtual class FTEController { public JobWrapper workCardJobStatus {get; set;} public FTEController() { - } public virtual PageReference goToEmployeeListView() { diff --git a/src/classes/FTECsvUploadController.cls b/src/classes/FTECsvUploadController.cls index 01398ed8..20296224 100644 --- a/src/classes/FTECsvUploadController.cls +++ b/src/classes/FTECsvUploadController.cls @@ -10,6 +10,9 @@ public class FTECsvUploadController extends FTEController { public Pagination fteStatusPagination { get; set; } public Map records { get; set; } + private Id contractId; + private Set employees; + private Decimal uploadedYear; private Map employeeMap; public FTECsvUploadController() { @@ -17,31 +20,30 @@ public class FTECsvUploadController extends FTEController { loadWorkCardJobStatus(); } - public List getFTEStatusRecords() { - List result = [SELECT Status__c, Status_Message__c, Line_Number__c, Line_Number_Text__c FROM FTE_Data_Record_Status__c - ORDER BY Line_Number__c LIMIT : this.fteStatusPagination.pageSize OFFSET : this.fteStatusPagination.getOffsetValue()]; - this.fteStatusPagination.handleResulSize([SELECT count() FROM FTE_Data_Record_Status__c]); - return result; - } + public Boolean processFTEDataRecords() { + Map mappedTemplates = new Map(); + for (FTE_Data_Record__c rec : [SELECT Id, Employee__c, Year__c, + Month_1__c, Month_2__c, Month_3__c, Month_4__c, Month_5__c, Month_6__c, + Month_7__c, Month_8__c, Month_9__c, Month_10__c, Month_11__c, Month_12__c, + Month_Updated_1__c, Month_Updated_2__c, Month_Updated_3__c, Month_Updated_4__c, Month_Updated_5__c, Month_Updated_6__c, + Month_Updated_7__c, Month_Updated_8__c, Month_Updated_9__c, Month_Updated_10__c, Month_Updated_11__c, Month_Updated_12__c + FROM FTE_Data_Record__c + WHERE Contract__c =: this.contractId AND Employee__c IN: this.employees AND Year__c =: this.uploadedYear]) { + mappedTemplates.put(rec.Employee__c, rec); + } - public void removeFTEDataStatusRecords() { - List toRemove = [SELECT Id FROM FTE_Data_Record_Status__c]; - if (toRemove.size() > 0) { - delete toRemove; - ApexPages.getMessages().clear(); + List finalDataList = new List(); + for (FTEUploadData rec : this.records.values()) { + finalDataList.add(rec.mergeData(mappedTemplates.get(rec.getEmployeeId()))); } - } - public Boolean processFTEDataRecords() { loadWorkCardJobStatus(); if (this.workCardJobStatus.isRunning == false) { - removeFTEDataStatusRecords(); // Remove status of last batch upload + upsert finalDataList; if (!Test.isRunningTest()) { - Database.executeBatch(new FTEHoursUploadBatch(this.records.values()), 1); - } else { - Database.executeBatch(new FTEHoursUploadBatch(this.records.values())); // no more then one batch execute can be invoked from test - } + Database.executeBatch(new FTETimeAllocator(true), 1); + } // In test we want test only parsing and how records was upserted, Time allocator has seprate tests loadWorkCardJobStatus(); ApexPages.getMessages().clear(); @@ -105,6 +107,7 @@ public class FTECsvUploadController extends FTEController { Integer csvYear = Integer.valueOf(numbers.get(1)); if (year == -1) { year = csvYear; + this.uploadedYear = csvYear; } if (!validateColumnHeaderData(year, csvYear, csvMonth, lineNum, strHelper, monthMapping)) { @@ -126,10 +129,15 @@ public class FTECsvUploadController extends FTEController { fteContract = getContract(contractName); if (fteContract == null) { return null; + } else if (fteContract.FTE_Tracker__c != 'Yes') { + ApexPages.addMessage(new ApexPages.message(ApexPages.severity.ERROR, 'Contract : "' + contractName + '" is not a FTE Tracker contract, please add it to Tracker before uploading CSV Template')); + return null; } + this.contractId = fteContract.Id; csvPattern = Pattern.compile('(\\d*\\.\\d+)|(\\d+\\.\\d*)'); this.records = new Map(); + this.employees = new Set(); loadEmployeeMap(); Boolean wasData = false; @@ -144,6 +152,7 @@ public class FTECsvUploadController extends FTEController { FTEUploadData emplDataRec = null; Id emplId = this.employeeMap.get(employeeName).Id; + this.employees.add(emplId); if (this.records.containsKey(emplId)) { emplDataRec = this.records.get(emplId); } else { @@ -157,12 +166,14 @@ public class FTECsvUploadController extends FTEController { } Integer index = monthMapping.get(i - 1 - columnsToSkip); - if (strHelper.isNumeric() || csvPattern.matcher(strHelper).matches()) { + if (strHelper.isNumeric() || csvPattern.matcher(strHelper).matches()) { // number 0 - ... emplDataRec.addMonthTime(index, Decimal.valueOf(strHelper)); wasData = true; - } else if (!String.isBlank(strHelper)) { + } else if (!String.isBlank(strHelper)) { // wrong number ApexPages.addMessage(new ApexPages.message(ApexPages.severity.ERROR, 'Line ' + lineNum + ' : ' + 'bad number format - ' + strHelper)); return null; + } else { // blank value we must set -1 to it will mean that threshold is removed + emplDataRec.addMonthTime(index, -1); } } this.records.put(emplId, emplDataRec); @@ -175,9 +186,10 @@ public class FTECsvUploadController extends FTEController { Boolean success = processFTEDataRecords(); if (success) { - ApexPages.addMessage(new ApexPages.message(ApexPages.severity.INFO, 'CSV file ' + fileName + ' was parsed. Upload job was scheduled, please wait for results.')); + ApexPages.addMessage(new ApexPages.message(ApexPages.severity.INFO, 'CSV file ' + fileName + ' was parsed. ' + + this.records.values().size() + ' records was added for ' + this.records.keySet().size() + ' employees.')); } else { - ApexPages.addMessage(new ApexPages.message(ApexPages.severity.ERROR, 'Cannot upload csv file. FTE Tracker is calculating time.')); + ApexPages.addMessage(new ApexPages.message(ApexPages.severity.ERROR, 'Cannot upload csv file. FTE Tracker is currently calculating time.')); } return null; @@ -189,7 +201,7 @@ public class FTECsvUploadController extends FTEController { } private DContract__c getContract(String contractName) { - List contracts = [SELECT Id, Name FROM DContract__c WHERE Name =: contractName]; + List contracts = [SELECT Id, Name, FTE_Tracker__c FROM DContract__c WHERE Name =: contractName]; if (contracts.size() > 0) { return contracts.get(0); } diff --git a/src/classes/FTEDataManager.cls b/src/classes/FTEDataManager.cls new file mode 100644 index 00000000..afa48c90 --- /dev/null +++ b/src/classes/FTEDataManager.cls @@ -0,0 +1,760 @@ +/** + * FTEDataManager for loading Employee time and managing it. It contains methods which allow moving time between contracts. + */ +public class FTEDataManager { + + private Boolean monthMode; + private SFDC_Employee__c employee; + private Integer fteMonth; + private Integer fteYear; + private Boolean skipWorkCard; + private List timeCardsToUpdate; + + public Integer employeeNetworkDays { get; set;} + public FTEEmployeeTime unassigned { get; set;} + public Map assignedMap { get; set;} + public Map unassignedMap { get; set;} + + public FTEDataManager(Integer year, Integer month, Id employeeId, Boolean skipWorkCard) { + this(year, employeeId, skipWorkCard); + this.fteMonth = month; + this.monthMode = true; + } + + public FTEDataManager(Integer year, Id employeeId, Boolean skipWorkCard) { + this.monthMode = false; + this.fteYear = year; + this.skipWorkCard = skipWorkCard; + this.assignedMap = new Map(); + this.unassignedMap = new Map(); + this.unassigned = new FTEEmployeeTime('Unassigned', null); // Sum of all unassigned employee contracts + this.employee = [SELECT Id, Name FROM SFDC_Employee__c WHERE Id =: employeeId LIMIT 1]; + } + + public FTEDataManager(Integer year, Id employeeId) { + this(year, employeeId, true); + } + + /** + * Helper method to remove negative time. Negative time can be possible when client in time cards was updated. + */ + public void removeNegativeTime() { + removeNegativeTime(this.fteMonth); + } + + public void removeNegativeTime(Integer month) { + + } + + /** + * Checks if time will moved to unassigned. Return true if time should be used to unassigned (removeTime should be used). + */ + public Boolean shouldUnassign(Id contractId, Decimal thresholdHours) { + return shouldUnassign(contractId, thresholdHours, this.fteMonth); + } + + public Boolean shouldUnassign(Id contractId, Decimal thresholdHours, Integer month) { + if (thresholdHours < 0) { + return false; + } + Decimal availableTime = this.unassigned.hoursArray[month - 1]; + Decimal missingTime = thresholdHours; + if (this.assignedMap.containsKey(contractId)) { + missingTime = thresholdHours - this.assignedMap.get(contractId).hoursArray[month - 1]; + } + + if (missingTime > 0) { + return false; + } + return true; + } + + /** + * Sets threshold time and moves hours if possible. Threshold is loaded from the uploaded CSV template + */ + public void setTime(Id contractId, Decimal thresholdHours) { + setTime(contractId, thresholdHours, this.fteMonth); + } + + public void setTime(Id contractId, Decimal thresholdHours, Integer month) { + if (thresholdHours < 0) { + return; + } + Decimal availableTime = this.unassigned.hoursArray[month - 1]; + Decimal missingTime = thresholdHours; + if (this.assignedMap.containsKey(contractId)) { + missingTime = thresholdHours - this.assignedMap.get(contractId).hoursArray[month - 1]; + } + + if (missingTime > 0) { + addTime(contractId, availableTime > missingTime ? missingTime : availableTime); + } else if (missingTime < 0) { + removeTime(contractId, (-1) * missingTime); + } + } + + /** + * Set given threshold in month and upserst template for blocking hours and future time allocation. + */ + public void upsertTimeTemplate(Id contractId, Decimal threshold, Integer month, Boolean reloadWorkCard) { + List templates = [SELECT Id, Month_1__c, Month_2__c, Month_3__c, Month_4__c, Month_5__c, Month_6__c, + Month_7__c, Month_8__c, Month_9__c, Month_10__c, Month_11__c, Month_12__c + FROM FTE_Data_Record__c + WHERE Contract__c =: contractId AND Employee__c =: this.employee.Id AND Year__c =: this.fteYear]; + + FTE_Data_Record__c template = null; + if (templates.size() > 0) { + template = templates.get(0); + } else { + template = new FTE_Data_Record__c(Contract__c = contractId, Employee__c = this.employee.Id, Year__c = this.fteYear); + } + + SObject templateSObj = (SObject) template; + templateSObj.put(FTETrackerHelper.getFieldName(month), threshold); + upsert template; + + if (this.assignedMap.containsKey(contractId)) { + this.assignedMap.get(contractId).templateArray[month - 1] = threshold; + } + + if (reloadWorkCard == true && this.skipWorkCard == false) { + recalculateWorkCardMonth(month); + } + } + + /** + * Set -1 in time template, it means that we remove blocking hours and future time allocation for given month and contract. + */ + public void removeTimeTemplate(Id contractId, Integer month) { + List templates = [SELECT Id, Month_1__c, Month_2__c, Month_3__c, Month_4__c, Month_5__c, Month_6__c, + Month_7__c, Month_8__c, Month_9__c, Month_10__c, Month_11__c, Month_12__c + FROM FTE_Data_Record__c + WHERE Contract__c =: contractId AND Employee__c =: this.employee.Id AND Year__c =: this.fteYear]; + if (templates.size() > 0) { + FTE_Data_Record__c template = templates.get(0); + SObject templateSObj = (SObject) template; + templateSObj.put(FTETrackerHelper.getFieldName(month), -1); + upsert template; + + if (this.assignedMap.containsKey(contractId)) { + this.assignedMap.get(contractId).templateArray[month - 1] = -1; + } + + if (this.skipWorkCard == false) { + recalculateWorkCardMonth(month); + } + } + } + + /** + * Move time to assigned. + */ + public void addTime(Id contractId, Decimal hours) { + addTime(contractId, hours, this.fteMonth); + } + + public void addTime(Id contractId, Decimal fteHours, Integer month) { + Date monthStart = Date.newInstance(this.fteYear, month, 1); + Date endMonth = Date.newInstance(this.fteYear, month, Date.daysInMonth(this.fteYear, month)); + this.timeCardsToUpdate = new List(); + + Decimal hoursToAssign = fteHours; + Decimal availableTime = this.unassigned.hoursArray[month - 1]; + hoursToAssign = availableTime >= hoursToAssign ? hoursToAssign : availableTime; + + // Step 1. If we have time between FTE contract we want remove such tags. + List timeCardsFromDB = [SELECT Id, Client__c, Client__r.FTE_Tracker__c, Client__r.Name, + Total__c, FTE_hours__c, FTE_Contract__c, + FTE_Contract__r.FTE_Tracker__c, FTE_Contract__r.Name, + Date__c FROM Time_Card__c + WHERE Employee__c =: this.employee.Id AND Client__c != null + AND FTE_Contract__r.FTE_Tracker__c = 'Yes' + AND Client__c =: contractId + AND Date__c >=: monthStart AND Date__c <=: endMonth]; + for (Time_Card__c tc : timeCardsFromDB) { + Decimal movedHours = tc.FTE_Hours__c; + Decimal hours = movedHours > hoursToAssign ? hoursToAssign : movedHours; + + if (hours > 0) { + tc.FTE_hours__c = tc.FTE_hours__c - hours; + hoursToAssign -= hours; + + if (this.assignedMap.containsKey(tc.FTE_Contract__c)) { + this.assignedMap.get(tc.FTE_Contract__c).hoursArray[month - 1] -= hours; + } + if (this.assignedMap.containsKey(contractId)) { + this.assignedMap.get(contractId).hoursArray[month - 1] += hours; + } + + if (tc.FTE_hours__c <= 0) { + tc.FTE_Contract__c = null; + tc.FTE_hours__c = 0; + } + this.timeCardsToUpdate.add(tc); + + if (hours > 0) { // Step 2. Check description below + hours = assignExistingTagToUnassigned(tc.FTE_Contract__c, hours, month, monthStart, endMonth); + } + + if (hours > 0) { // Step 3. Check description below + hours = assignExistingTagsFromUnassigned(tc.FTE_Contract__c, hours, month, monthStart, endMonth); + } + + if (hours > 0) { // Step 4. Check description below + hours = assignWithNewTag(tc.FTE_Contract__c, hours, month, monthStart, endMonth); + } + + if (hours > 0) { // Step 5. Check description below + hours = assignWithNewTagAndTimeCard(tc.FTE_Contract__c, hours, month, monthStart); + } + + if (hoursToAssign <= 0) { + hoursToAssign = 0; + break; + } + } + } + + // Step 2. If we have moved hours from assigned to unassigned we want take these hours back. + if (hoursToAssign > 0) { + hoursToAssign = assignExistingTagToUnassigned(contractId, hoursToAssign, month, monthStart, endMonth); + } + + // Step 3. If we already have tag from unassigned we want take more hours from that tag. + if (hoursToAssign > 0) { + hoursToAssign = assignExistingTagsFromUnassigned(contractId, hoursToAssign, month, monthStart, endMonth); + } + + // Step 4. If we still need hours we will take time cards without any tag and add tag there. + if (hoursToAssign > 0) { + hoursToAssign = assignWithNewTag(contractId, hoursToAssign, month, monthStart, endMonth); + } + + // Step 5. If we have available time we need create empty time card with tag. + if (hoursToAssign > 0) { + hoursToAssign = assignWithNewTagAndTimeCard(contractId, hoursToAssign, month, monthStart); + } + + if (this.timeCardsToUpdate.size() > 0) { + upsert this.timeCardsToUpdate; + this.timeCardsToUpdate.clear(); + } + + if (this.skipWorkCard == false) { + recalculateWorkCardMonth(month); + } + } + + /** + * Move time to unassigned. + */ + public void removeTime(Id contractId, Decimal hours) { + removeTime(contractId, hours, this.fteMonth); + } + + public void removeTime(Id contractId, Decimal fteHours, Integer month) { + Date monthStart = Date.newInstance(this.fteYear, month, 1); + Date endMonth = Date.newInstance(this.fteYear, month, Date.daysInMonth(this.fteYear, month)); + this.timeCardsToUpdate = new List(); + + Decimal hoursToUnassign = fteHours; + if (this.assignedMap.containsKey(contractId)) { + Decimal availableTime = this.assignedMap.get(contractId).hoursArray[month - 1]; + hoursToUnassign = availableTime >= hoursToUnassign ? hoursToUnassign : availableTime; + } + + // Step 1. If we have time between FTE contract we want remove such tags. + List timeCardsFromDB = [SELECT Id, Client__c, Client__r.FTE_Tracker__c, Client__r.Name, + Total__c, FTE_hours__c, FTE_Contract__c, + FTE_Contract__r.FTE_Tracker__c, FTE_Contract__r.Name, + Date__c FROM Time_Card__c + WHERE Employee__c =: this.employee.Id AND Client__c != null + AND Client__r.FTE_Tracker__c = 'Yes' + AND FTE_Contract__c =: contractId + AND Date__c >=: monthStart AND Date__c <=: endMonth]; + for (Time_Card__c tc : timeCardsFromDB) { + Decimal movedHours = tc.FTE_Hours__c; + Decimal hours = movedHours > hoursToUnassign ? hoursToUnassign : movedHours; + + if (hours > 0) { + tc.FTE_hours__c = tc.FTE_hours__c - hours; + hoursToUnassign -= hours; + if (this.assignedMap.containsKey(contractId)) { + this.assignedMap.get(contractId).hoursArray[month - 1] -= hours; + } + if (this.assignedMap.containsKey(tc.Client__c)) { + this.assignedMap.get(tc.Client__c).hoursArray[month - 1] += hours; + } + + if (tc.FTE_hours__c <= 0) { + tc.FTE_Contract__c = null; + tc.FTE_hours__c = 0; + } + this.timeCardsToUpdate.add(tc); + + if (hours > 0) { // Step 2. Check description below + hours = unassignExistingTagToFTE(tc.Client__c, hours, month, monthStart, endMonth); + } + + if (hours > 0) { // Step 3. Check description below + hours = unassignExistingTagFromFTE(tc.Client__c, hours, month, monthStart, endMonth); + } + + if (hours > 0) { // Step 4. Check description below + hours = unassignWithNewTag(tc.Client__c, hours, month, monthStart, endMonth); + } + + if (hours > 0) { // Step 5. Check description below + hours = unassignWithNewTagAndTimeCard(tc.Client__c, hours, month, monthStart); + } + + if (hoursToUnassign <= 0) { + hoursToUnassign = 0; + break; + } + } + } + + // Step 2. If we have moved hours from unassigned to assigned we want take these hours back, we want decrease number of tags. + if (hoursToUnassign > 0) { + hoursToUnassign = unassignExistingTagToFTE(contractId, hoursToUnassign, month, monthStart, endMonth); + } + + Decimal hoursMapHelper = hoursToUnassign; + // Step 3. If we already have tag from assigned we want take more hours from that tag, we want decrease number of tags. + if (hoursToUnassign > 0) { + hoursToUnassign = unassignExistingTagFromFTE(contractId, hoursToUnassign, month, monthStart, endMonth); + } + + // Step 4. If we don't have any tag we need add one in time cards. + if (hoursToUnassign > 0) { + hoursToUnassign = unassignWithNewTag(contractId, hoursToUnassign, month, monthStart, endMonth); + } + + // Step 5. If we don't have any empty time card we need create a empty one to move hours. + if (hoursToUnassign > 0) { + hoursToUnassign = unassignWithNewTagAndTimeCard(contractId, hoursToUnassign, month, monthStart); + } + + if (this.timeCardsToUpdate.size() > 0) { + upsert this.timeCardsToUpdate; + this.timeCardsToUpdate.clear(); + } + + if (this.skipWorkCard == false) { + recalculateWorkCardMonth(month); + } + } + + private Decimal assignExistingTagToUnassigned(Id contractId, Decimal timeToAssign, Integer month, Date monthStart, Date endMonth) { + List timeCardsFromDB = [SELECT Id, Client__c, Client__r.FTE_Tracker__c, Client__r.Name, + Total__c, FTE_hours__c, FTE_Contract__c, + FTE_Contract__r.FTE_Tracker__c, FTE_Contract__r.Name, + Date__c FROM Time_Card__c + WHERE Employee__c =: this.employee.Id AND Client__c != null + AND Client__r.FTE_Tracker__c = 'Yes' AND FTE_Hours__c > 0 + AND Client__c =: contractId AND FTE_Contract__c != null + AND (FTE_Contract__r.FTE_Tracker__c = 'No' OR FTE_Contract__r.FTE_Tracker__c = '') + AND Date__c >=: monthStart AND Date__c <=: endMonth]; + + for (Time_Card__c tc : timeCardsFromDB) { + Decimal freeHours = this.unassignedMap.get(tc.FTE_Contract__c).hoursArray[month - 1]; + Decimal movedHours = tc.FTE_Hours__c; + Decimal hours = movedHours > freeHours ? freeHours : movedHours; + + if (hours > 0) { // we try remove tag from tc + Decimal toAssign = timeToAssign > hours ? hours : timeToAssign; + tc.FTE_hours__c = tc.FTE_hours__c - toAssign; + timeToAssign -= toAssign; + + this.unassignedMap.get(tc.FTE_Contract__c).hoursArray[month - 1] -= toAssign; + this.unassigned.hoursArray[month - 1] -= toAssign; + if (this.assignedMap.containsKey(contractId)) { + this.assignedMap.get(contractId).hoursArray[month - 1] += toAssign; + } else { + FTEEmployeeTime fteData = new FTEEmployeeTime('', contractId); + fteData.hoursArray[month - 1] += toAssign; + this.assignedMap.put(contractId, fteData); + } + + if (tc.FTE_hours__c <= 0) { + tc.FTE_Contract__c = null; + } + this.timeCardsToUpdate.add(tc); + if (timeToAssign <= 0) { + timeToAssign = 0; + break; + } + } + } + + return timeToAssign; + } + + private Decimal assignExistingTagsFromUnassigned(Id contractId, Decimal timeToAssign, Integer month, Date monthStart, Date endMonth) { + List timeCardsFromDB = [SELECT Id, Client__c, Client__r.FTE_Tracker__c, Client__r.Name, + Total__c, FTE_hours__c, FTE_Contract__c, + FTE_Contract__r.FTE_Tracker__c, FTE_Contract__r.Name, + Date__c FROM Time_Card__c + WHERE Employee__c =: this.employee.Id AND Client__c != null + AND (Client__r.FTE_Tracker__c = 'No' OR Client__r.FTE_Tracker__c = '') + AND FTE_Contract__c =: contractId + AND Date__c >=: monthStart AND Date__c <=: endMonth]; + + for (Time_Card__c tc : timeCardsFromDB) { + Decimal hours = this.unassignedMap.get(tc.Client__c).hoursArray[month - 1]; + if (hours > 0) { + Decimal toAssign = timeToAssign > hours ? hours : timeToAssign; + tc.FTE_hours__c = tc.FTE_hours__c != null ? tc.FTE_hours__c + toAssign : toAssign; + timeToAssign -= toAssign; + + this.unassignedMap.get(tc.Client__c).hoursArray[month - 1] -= toAssign; + this.unassigned.hoursArray[month - 1] -= toAssign; + if (this.assignedMap.containsKey(contractId)) { + this.assignedMap.get(contractId).hoursArray[month - 1] += toAssign; + } else { + FTEEmployeeTime fteData = new FTEEmployeeTime('', contractId); + fteData.hoursArray[month - 1] += toAssign; + this.assignedMap.put(contractId, fteData); + } + + this.timeCardsToUpdate.add(tc); + if (timeToAssign <= 0) { + timeToAssign = 0; + break; + } + } + } + return timeToAssign; + } + + private Decimal assignWithNewTag(Id contractId, Decimal timeToAssign, Integer month, Date monthStart, Date endMonth) { + List timeCardsFromDB = [SELECT Id, Client__c, Client__r.FTE_Tracker__c, Client__r.Name, + Total__c, FTE_hours__c, FTE_Contract__c, + FTE_Contract__r.FTE_Tracker__c, FTE_Contract__r.Name, + Date__c FROM Time_Card__c + WHERE Employee__c =: this.employee.Id AND Client__c != null + AND (Client__r.FTE_Tracker__c = 'No' OR Client__r.FTE_Tracker__c = '') + AND (FTE_Contract__c = null OR FTE_Contract__c = '') + AND Date__c >=: monthStart AND Date__c <=: endMonth]; + + Set emptyContracts = new Set(); + for (Time_Card__c tc : timeCardsFromDB) { + if (emptyContracts.contains(tc.Client__c)) { + continue; + } + + Decimal hours = this.unassignedMap.get(tc.Client__c).hoursArray[month - 1]; + if (hours > 0) { + Decimal toAssign = timeToAssign > hours ? hours : timeToAssign; + tc.FTE_hours__c = tc.FTE_hours__c != null ? tc.FTE_hours__c + toAssign : toAssign; + tc.FTE_Contract__c = contractId; + timeToAssign -= toAssign; + + this.unassignedMap.get(tc.Client__c).hoursArray[month - 1] -= toAssign; + this.unassigned.hoursArray[month - 1] -= toAssign; + if (this.assignedMap.containsKey(contractId)) { + this.assignedMap.get(contractId).hoursArray[month - 1] += toAssign; + } else { + FTEEmployeeTime fteData = new FTEEmployeeTime('', contractId); + fteData.hoursArray[month - 1] += toAssign; + this.assignedMap.put(contractId, fteData); + } + + this.timeCardsToUpdate.add(tc); + if (timeToAssign <= 0) { + timeToAssign = 0; + break; + } + + emptyContracts.add(tc.Client__c); + } else { + emptyContracts.add(tc.Client__c); + } + } + + return timeToAssign; + } + + private Decimal assignWithNewTagAndTimeCard(Id contractId, Decimal timeToAssign, Integer month, Date monthStart) { + for (Id conId : this.unassignedMap.keySet()) { + Decimal hours = this.unassignedMap.get(conId).hoursArray[month - 1]; + if (hours > 0) { + Decimal toAssign = timeToAssign > hours ? hours : timeToAssign; + Time_Card__c tc = new Time_Card__c(Client__c = conId, Employee__c = this.employee.Id, Date__c = monthStart, + FTE_only__c = true, Total__c = 0, FTE_hours__c = toAssign, + FTE_Contract__c = contractId); + timeToAssign -= toAssign; + + this.unassignedMap.get(conId).hoursArray[month - 1] -= toAssign; + this.unassigned.hoursArray[month - 1] -= toAssign; + if (this.assignedMap.containsKey(contractId)) { + this.assignedMap.get(contractId).hoursArray[month - 1] += toAssign; + } else { + FTEEmployeeTime fteData = new FTEEmployeeTime('', contractId); + fteData.hoursArray[month - 1] += toAssign; + this.assignedMap.put(contractId, fteData); + } + + this.timeCardsToUpdate.add(tc); + if (timeToAssign <= 0) { + break; + } + } + } + return 0; + } + + private Decimal unassignExistingTagToFTE(Id contractId, Decimal timeToUnassign, Integer month, Date monthStart, Date endMonth) { + List timeCardsFromDB = [SELECT Id, Client__c, Client__r.FTE_Tracker__c, Client__r.Name, + Total__c, FTE_hours__c, FTE_Contract__c, + FTE_Contract__r.FTE_Tracker__c, FTE_Contract__r.Name, + Date__c FROM Time_Card__c + WHERE Employee__c =: this.employee.Id AND Client__c != null + AND (Client__r.FTE_Tracker__c = 'No' OR Client__r.FTE_Tracker__c = '') + AND FTE_Contract__c =: contractId + AND Date__c >=: monthStart AND Date__c <=: endMonth]; + + for (Time_Card__c tc : timeCardsFromDB) { + Decimal movedHours = tc.FTE_Hours__c; + Decimal hours = movedHours > timeToUnassign ? timeToUnassign : movedHours; + + if (hours > 0) { + tc.FTE_hours__c = tc.FTE_hours__c - hours; + timeToUnassign -= hours; + + this.assignedMap.get(contractId).hoursArray[month - 1] -= hours; + this.unassignedMap.get(tc.Client__c).hoursArray[month - 1] += hours; + this.unassigned.hoursArray[month - 1] += hours; + + if (tc.FTE_hours__c <= 0) { + tc.FTE_Contract__c = null; + tc.FTE_hours__c = 0; + } + + this.timeCardsToUpdate.add(tc); + if (timeToUnassign <= 0) { + timeToUnassign = 0; + break; + } + } + } + return timeToUnassign; + } + + private Decimal unassignExistingTagFromFTE(Id contractId, Decimal timeToUnassign, Integer month, Date monthStart, Date endMonth) { + List timeCardsFromDB = [SELECT Id, Client__c, Client__r.FTE_Tracker__c, Client__r.Name, + Total__c, FTE_hours__c, FTE_Contract__c, + FTE_Contract__r.FTE_Tracker__c, FTE_Contract__r.Name, + Date__c FROM Time_Card__c + WHERE Employee__c =: this.employee.Id AND Client__c != null + AND Client__c =: contractId AND FTE_Contract__c != null + AND (FTE_Contract__r.FTE_Tracker__c = 'No' OR FTE_Contract__r.FTE_Tracker__c = '') + AND Date__c >=: monthStart AND Date__c <=: endMonth LIMIT 1]; + if (timeCardsFromDB.size() > 0) { + Time_Card__c tc = timeCardsFromDB.get(0); + tc.FTE_hours__c = tc.FTE_hours__c != null ? tc.FTE_hours__c + timeToUnassign : timeToUnassign; + this.timeCardsToUpdate.add(tc); + + this.assignedMap.get(contractId).hoursArray[month - 1] -= timeToUnassign; + this.unassignedMap.get(tc.FTE_Contract__c).hoursArray[month - 1] += timeToUnassign; + this.unassigned.hoursArray[month - 1] += timeToUnassign; + + timeToUnassign = 0; + } + + return timeToUnassign; + } + + private Decimal unassignWithNewTag(Id contractId, Decimal timeToUnassign, Integer month, Date monthStart, Date endMonth) { + List timeCardsFromDB = [SELECT Id, Client__c, Client__r.FTE_Tracker__c, Client__r.Name, + Total__c, FTE_hours__c, FTE_Contract__c, + FTE_Contract__r.FTE_Tracker__c, FTE_Contract__r.Name, + Date__c FROM Time_Card__c + WHERE Employee__c =: this.employee.Id AND Client__c != null + AND Client__c =: contractId + AND FTE_Contract__c = null + AND Date__c >=: monthStart AND Date__c <=: endMonth LIMIT 1]; + + if (timeCardsFromDB.size() > 0) { + Time_Card__c tc = timeCardsFromDB.get(0); + tc.FTE_Contract__c = this.unassignedMap.values()[getContractIndex()].objId; + tc.FTE_hours__c = timeToUnassign; + this.timeCardsToUpdate.add(tc); + + this.assignedMap.get(contractId).hoursArray[month - 1] -= timeToUnassign; + this.unassignedMap.get(tc.FTE_Contract__c).hoursArray[month - 1] += timeToUnassign; + this.unassigned.hoursArray[month - 1] += timeToUnassign; + + timeToUnassign = 0; + } + + return timeToUnassign; + } + + private Decimal unassignWithNewTagAndTimeCard(Id contractId, Decimal timeToUnassign, Integer month, Date monthStart) { + Time_Card__c tc = new Time_Card__c(Client__c = contractId, Employee__c = this.employee.Id, Date__c = monthStart, + FTE_only__c = true, Total__c = 0, FTE_hours__c = timeToUnassign, + FTE_Contract__c = this.unassignedMap.values()[getContractIndex()].objId); + this.timeCardsToUpdate.add(tc); + + this.assignedMap.get(contractId).hoursArray[month - 1] -= timeToUnassign; + this.unassignedMap.get(tc.FTE_Contract__c).hoursArray[month - 1] += timeToUnassign; + this.unassigned.hoursArray[month - 1] += timeToUnassign; + + return 0; + } + + /** + * Loads employee time for given year / month + */ + public void loadEmployeeTime() { + List timeCards = null; + if (this.monthMode == true) { // only one month data + timeCards = [SELECT Id, Client__c, Client__r.FTE_Tracker__c, Client__r.Name, + Total__c, FTE_hours__c, FTE_Contract__c, + FTE_Contract__r.FTE_Tracker__c, FTE_Contract__r.Name, + Date__c FROM Time_Card__c + WHERE Employee__c =: this.employee.Id AND Client__c != null + AND CALENDAR_YEAR(Date__c) =: this.fteYear AND CALENDAR_MONTH(Date__c) =: this.fteMonth + ORDER BY Client__r.Name]; + } else { // year data used mostly by the UI + timeCards = [SELECT Id, Client__c, Client__r.FTE_Tracker__c, Client__r.Name, + Total__c, FTE_hours__c, FTE_Contract__c, + FTE_Contract__r.FTE_Tracker__c, FTE_Contract__r.Name, + Date__c FROM Time_Card__c + WHERE Employee__c =: this.employee.Id AND Client__c != null + AND CALENDAR_YEAR(Date__c) =: this.fteYear + ORDER BY Client__r.Name]; + } + + FTEEmployeeTime tmpHelper; + for (Time_Card__c timeCard : timeCards) { + calculateLoggedTime(timeCard); // sum hours and moved hours + } + } + + /** + * Loads time templates for given year and employee. + */ + public void loadTemplateTime() { + Integer monthIndex = 1; + Integer maxMonth = 12; + if (this.monthMode == true) { + monthIndex = this.fteMonth; + maxMonth = this.fteMonth; + } + for (FTE_Data_Record__c futureTemplate : [SELECT Contract__c, Contract__r.Name, Employee__c, Month_1__c, Month_2__c, Month_3__c, Month_4__c, + Month_5__c, Month_6__c, Month_7__c, Month_8__c, Month_9__c, Month_10__c, Month_11__c, Month_12__c + FROM FTE_Data_Record__c + WHERE Employee__c =: this.employee.Id AND Year__c =: this.fteYear AND Contract__r.FTE_Tracker__c = 'Yes']) { + SObject templateSObj = (SObject) futureTemplate; + + for (Integer month = monthIndex; month <= maxMonth; month++) { + Decimal monthValue = (Decimal) templateSObj.get(FTETrackerHelper.getFieldName(month)); + if (monthValue == -1) { + continue; + } + + if (!this.assignedMap.containsKey(futureTemplate.Contract__c)) { + this.assignedMap.put(futureTemplate.Contract__c, new FTEEmployeeTime(futureTemplate.Contract__r.Name, futureTemplate.Contract__c)); + } + + FTEEmployeeTime tmpHelper = this.assignedMap.get(futureTemplate.Contract__c); + + if (tmpHelper.templateArray[month - 1] == -1) { + tmpHelper.templateArray[month - 1] = 0; + } + tmpHelper.templateArray[month - 1] += monthValue; + } + } + } + + /** + * Recalculates Employee Work Month. Work Card object is used by the Employee List View for better performance. + */ + public void recalculateWorkCardMonth(Integer month) { + List employeeWorkCards = [SELECT Id, Total__c, Month_1__c, Month_2__c, Month_3__c, Month_4__c, Month_5__c, Month_6__c, + Month_7__c, Month_8__c, Month_9__c, Month_10__c, Month_11__c, Month_12__c, + Month_Future_1__c, Month_Future_2__c, Month_Future_3__c, Month_Future_4__c, Month_Future_5__c, Month_Future_6__c, + Month_Future_7__c, Month_Future_8__c, Month_Future_9__c, Month_Future_10__c, Month_Future_11__c, Month_Future_12__c, + Month_Blocked_1__c, Month_Blocked_2__c, Month_Blocked_3__c, Month_Blocked_4__c, Month_Blocked_5__c, Month_Blocked_6__c, + Month_Blocked_7__c, Month_Blocked_8__c, Month_Blocked_9__c, Month_Blocked_10__c, Month_Blocked_11__c, Month_Blocked_12__c + FROM FTE_Work_Card__c WHERE Employee__c =: this.employee.Id AND Year__c =: this.fteYear]; + if (employeeWorkCards.size() > 0) { + FTE_Work_Card__c cardToUpdate = employeeWorkCards.get(0); + SObject workCardSObj = (SObject) cardToUpdate; + + Decimal oldValue = (Decimal) workCardSObj.get(FTETrackerHelper.getFieldName(month)); + Decimal oldTotal = (Decimal) workCardSObj.get('Total__c'); + workCardSObj.put(FTETrackerHelper.getFieldTemplateFutureName(month), false); + workCardSObj.put(FTETrackerHelper.getFieldTemplateBlockedName(month), false); + + Decimal newValue = 0; + + for (Id contractId : this.assignedMap.keySet()) { + FTEEmployeeTime emplTime = this.assignedMap.get(contractId); + Decimal templateTime = (emplTime.templateArray[month - 1] != -1 ? emplTime.templateArray[month - 1] * 8 : -1); + Decimal loggedTime = emplTime.hoursArray[month - 1]; + Decimal finalResult = 0; + + if (templateTime != -1 && templateTime > loggedTime) { + newValue += templateTime; + workCardSObj.put(FTETrackerHelper.getFieldTemplateFutureName(month), true); // UI metadata, increase performance + } else if (templateTime != -1 && templateTime == loggedTime) { + newValue += loggedTime; + workCardSObj.put(FTETrackerHelper.getFieldTemplateBlockedName(month), true); // UI metadata increase performance + } else { + newValue += loggedTime; + } + } + + newValue = FTETrackerHelper.roundtoDays(newValue); + workCardSObj.put(FTETrackerHelper.getFieldName(month), newValue); + workCardSObj.put('Total__c', (oldTotal - oldValue + newValue)); + update cardToUpdate; + } + } + + private void calculateLoggedTime(Time_Card__c timeCard) { + Decimal loggedTime = (timeCard.Total__c != null ? timeCard.Total__c : 0); + Decimal movedTime = (timeCard.FTE_hours__c != null ? timeCard.FTE_hours__c : 0); + + // "Moved from" part this.contractsTime.add(this.unassigned); + sumLoggedTime(timeCard.Client__r.FTE_Tracker__c == 'Yes', timeCard.Client__c, timeCard.Client__r.Name, (loggedTime - movedTime), timeCard.Date__c.month()); + // If no FTE Tag values we don't need to process moved hours + if (timeCard.FTE_Contract__c == null || movedTime == 0) { + return; + } + // "Moved to" part + sumLoggedTime(timeCard.FTE_Contract__r.FTE_Tracker__c == 'Yes', timeCard.FTE_Contract__c, timeCard.FTE_Contract__r.Name, movedTime, timeCard.Date__c.month()); + } + + private void sumLoggedTime(Boolean fteTracker, Id clientId, String clientName, Decimal loggedHours, Integer month) { + FTEEmployeeTime tmpHelper = this.unassigned; + if (fteTracker == true) { + if (!this.assignedMap.containsKey(clientId)) { + this.assignedMap.put(clientId, new FTEEmployeeTime(clientName, clientId)); + } + tmpHelper = this.assignedMap.get(clientId); + tmpHelper.hoursArray[month - 1] += loggedHours; + } else { + if (!this.unassignedMap.containsKey(clientId)) { + this.unassignedMap.put(clientId, new FTEEmployeeTime(clientName, clientId)); + } + tmpHelper.hoursArray[month - 1] += loggedHours; + tmpHelper = this.unassignedMap.get(clientId); + tmpHelper.hoursArray[month - 1] += loggedHours; + } + } + + private Integer getContractIndex() { + Integer upperLimit = this.unassignedMap.size(); + if (upperLimit == 0) { + ApexPages.addMessage(new ApexPages.message(ApexPages.severity.ERROR, 'Cannot find unassigned contract')); + return 0; + } + Integer rand = Math.round(Math.random()*1000); + return Math.mod(rand, upperLimit); + } +} \ No newline at end of file diff --git a/src/classes/FTEDataManager.cls-meta.xml b/src/classes/FTEDataManager.cls-meta.xml new file mode 100644 index 00000000..cbddff8c --- /dev/null +++ b/src/classes/FTEDataManager.cls-meta.xml @@ -0,0 +1,5 @@ + + + 38.0 + Active + diff --git a/src/classes/FTEEmployeeController.cls b/src/classes/FTEEmployeeController.cls index 35ad23ea..6e61b180 100644 --- a/src/classes/FTEEmployeeController.cls +++ b/src/classes/FTEEmployeeController.cls @@ -4,30 +4,23 @@ public class FTEEmployeeController extends FTEController { private Id employeeId; - private Integer employeeNetworkDays = 0; + public SFDC_Employee__c employee { get; set; } - public Id contractId { get; set;} - public Integer fteYear { get; set;} - public Integer exportMonth { get; set;} - public SFDC_Employee__c employee { get; set;} + public FTEDataManager fteTimeManager { get; set; } + public FTEEmployeeTime totalAssignedDays { get; set; } + public FTEEmployeeTime totalDaysWorked { get; set; } + public List contractsTime { get; set; } - public FTETimeManager fteTimeManager { get; set;} - public FTEEmployeeTime totalAssignedDays { get; set;} - public FTEEmployeeTime totalDaysWorked { get; set;} - public List contractsTime { get; set;} + public List fteContracts { get; set; } + public List fteContractsOptions { get; set; } - public List fteContracts { get; set;} - public List fteContractsOptions { get;set; } - public Id selectedFteContract { get; set;} - public String fteDays { get; set;} - public Integer employeeMonth { get; set;} - public Decimal fteHoursMax { get; set;} - public Decimal fteDaysMax { get; set;} - public Decimal userAvailableDays { get; set;} - public Boolean assignViewError { get; set;} - public Boolean notValidDays {get; set;} - public String monthName {get; set;} - public String contractName { get; set;} + public Integer employeeNetworkDays { get; set; } + public Id contractId { get; set; } + public Integer fteYear { get; set; } + public Integer exportMonth { get; set; } + public Id selectedFteContract { get; set; } + public String fteDays { get; set; } + public Integer employeeMonth { get; set; } public FTEEmployeeController() { String emplId = ApexPages.currentPage().getParameters().get('employeeId'); @@ -41,239 +34,190 @@ public class FTEEmployeeController extends FTEController { } else { this.fteYear = Date.today().year(); } - } - public void initFteEmployeeView() { - if (this.employeeId == null) { - ApexPages.addMessage(new ApexPages.message(ApexPages.severity.ERROR, 'Empty Employee ID')); - return; + this.fteContracts = [SELECT Id, Name FROM DContract__c WHERE FTE_Tracker__c = 'Yes' ORDER BY Name]; + this.fteContractsOptions = new List(); + for (DContract__c con : this.fteContracts) { + this.fteContractsOptions.add(new SelectOption(con.Id, con.Name)); } List emplList = [SELECT Id, Name, Hire_Date__c FROM SFDC_Employee__c WHERE Id =: this.employeeId LIMIT 1]; if (emplList.size() > 0) { this.employee = emplList.get(0); } - this.employeeNetworkDays = FTETrackerHelper.getNetworkDays(this.employee.Hire_Date__c, this.fteYear); - this.fteTimeManager = new FTETimeManager(this.employee, this.fteYear); - this.fteTimeManager.loadEmployeeTime(); - buildUITableData(); - } - - public override PageReference goToEmployeeListView () { - PageReference pageRef = Page.FTE_Employee_List_View; - if (Date.today().year() != this.fteYear) { - pageref.getParameters().put('fteYear', String.valueOf(this.fteYear)); - } - return pageRef; } - public PageReference goToIndividualProjectView() { - PageReference pageRef = Page.FTE_Individual_Project_View; - pageref.getParameters().put('contractId', this.contractId); - pageref.getParameters().put('fteYear', String.valueOf(this.fteYear)); - return pageref; - } - - public PageReference goToTimeCardView() { - PageReference pageRef = null; - pageRef = Page.FTE_Time_Card_View; - pageref.getParameters().put('employeeId', this.employeeId); - pageref.getParameters().put('fteYear', String.valueOf(this.fteYear)); - pageref.getParameters().put('month', String.valueOf(this.exportMonth)); - return pageRef; - } - - public void loadEmployeeMonth() { - this.assignViewError = false; - this.notValidDays = false; - this.fteDays = '0.25'; - this.userAvailableDays = 0; - - if (this.employeeMonth != null) { - this.fteDaysMax = this.fteTimeManager.unassigned.daysArray[this.employeeMonth]; - this.fteHoursMax = this.fteTimeManager.unassigned.hoursArray[this.employeeMonth]; - this.monthName = DateTime.newInstance(this.fteYear, this.employeeMonth + 1, 1).format('MMMM yyyy'); - this.userAvailableDays = 21 - this.totalAssignedDays.daysArray[this.employeeMonth] > 0 ? 21 - - this.totalAssignedDays.daysArray[this.employeeMonth] : 0; - Decimal helperDecimal = employeeNetworkDays - this.totalAssignedDays.daysArray[12] > 0 ? - employeeNetworkDays - this.totalAssignedDays.daysArray[12] : 0; - this.userAvailableDays = this.userAvailableDays < helperDecimal ? this.userAvailableDays : helperDecimal; - } else { - ApexPages.addMessage(new ApexPages.message(ApexPages.severity.ERROR, 'Cannot load month number')); - this.assignViewError = true; - this.fteHoursMax = 0; + public void initFteEmployeeView() { + if (this.employeeId == null) { + ApexPages.addMessage(new ApexPages.message(ApexPages.severity.ERROR, 'Empty Employee ID')); + return; } - this.fteContracts = [SELECT Id, Name FROM DContract__c WHERE FTE_Tracker__c = 'Yes' ORDER BY Name]; - this.fteContractsOptions = new List(); - for (DContract__c con : this.fteContracts) { - this.fteContractsOptions.add(new SelectOption(con.Id, con.Name)); - } - } + loadWorkCardJobStatus(); - public void loadEmployeeUnassMonth() { - this.assignViewError = false; - this.notValidDays = false; - this.fteDays = '0.25'; - if (this.employeeMonth != null && this.contractId != null) { - this.contractName = this.fteTimeManager.assignedMap.get(this.contractId).name; - this.fteHoursMax = this.fteTimeManager.assignedMap.get(this.contractId).hoursArray[this.employeeMonth]; - this.fteDaysMax = this.fteTimeManager.assignedMap.get(this.contractId).daysArray[this.employeeMonth]; - this.monthName = DateTime.newInstance(this.fteYear, this.employeeMonth + 1, 1).format('MMMM yyyy'); - } else { - ApexPages.addMessage(new ApexPages.message(ApexPages.severity.ERROR, 'Cannot load contract month')); - this.assignViewError = true; - this.fteHoursMax = 0; + if (workCardJobStatus.isRunning == false) { + this.fteTimeManager = new FTEDataManager(this.fteYear, this.employee.Id, false); + this.fteTimeManager.loadEmployeeTime(); // loads employee time cards with tags + this.fteTimeManager.loadTemplateTime(); // loads uploaded csv templates to allocate future time + buildUITableData(); } } - public void moveTimeToUnassigned() { - if (notValidDays) { + public void setThreshold() { + loadWorkCardJobStatus(); + if (workCardJobStatus.isRunning == true) { + ApexPages.addMessage(new ApexPages.message(ApexPages.severity.ERROR, 'Cannot manage time template, FTE Tracker is currently busy.')); return; } - this.assignViewError = false; - if (this.employeeMonth == null) { - ApexPages.addMessage(new ApexPages.message(ApexPages.severity.ERROR, 'Cannot load month number')); - this.assignViewError = true; + if (!FTETrackerHelper.isContractFTE(this.contractId)) { + ApexPages.addMessage(new ApexPages.message(ApexPages.severity.ERROR, 'Contract is not in FTE Tracker, try refresh page.')); return; } - - Decimal fteDaysdecimal = this.fteDays != null && this.fteDays != '' ? Decimal.valueOf(this.fteDays) : 0; - if (this.fteDaysMax < fteDaysdecimal || fteDaysdecimal < 0) { - ApexPages.addMessage(new ApexPages.message(ApexPages.severity.ERROR, 'Too much hours to assign / hours cannot be negative')); - this.assignViewError = true; - return; + if (this.fteDays != null && this.fteDays != '') { + Decimal value = Decimal.valueOf(this.fteDays); + if (value >= 0) { + FTEDataManager manager = new FTEDataManager(this.fteYear, this.employeeMonth, this.employee.Id, false); + manager.upsertTimeTemplate(this.contractId, value, this.employeeMonth, false); //set time will refresh work card + manager.loadEmployeeTime(); + manager.loadTemplateTime(); + manager.setTime(this.contractId, value * 8); + } else { + resetThreshold(this.contractId, this.employeeMonth); + } + } else { + resetThreshold(this.contractId, this.employeeMonth); } + initFteEmployeeView(); + } + + public void resetThreshold(Id resetContract, Integer resetMonth) { + this.fteTimeManager.removeTimeTemplate(resetContract, resetMonth); + } - if (String.isEmpty(this.contractId)) { - ApexPages.addMessage(new ApexPages.message(ApexPages.severity.ERROR, 'Cannot find FTE Contract')); - this.assignViewError = true; + public void assignTime() { + loadWorkCardJobStatus(); + if (workCardJobStatus.isRunning == true) { + ApexPages.addMessage(new ApexPages.message(ApexPages.severity.ERROR, 'Cannot assign time, FTE Tracker is currently busy.')); return; } - - if (fteDaysdecimal == 0) { - ApexPages.addMessage(new ApexPages.message(ApexPages.severity.ERROR, 'Cannot move 0 hours')); - this.assignViewError = true; + if (!FTETrackerHelper.isContractFTE(this.selectedFteContract)) { + ApexPages.addMessage(new ApexPages.message(ApexPages.severity.ERROR, 'Contract is not in FTE Tracker, try refresh page.')); return; } - try { - Decimal hoursToUnassign = fteDaysdecimal * 8; - if (this.fteHoursMax < hoursToUnassign) { - hoursToUnassign = this.fteHoursMax; + if (this.fteDays != null && this.fteDays != '') { + Decimal value = Decimal.valueOf(this.fteDays); + if (value > 0) { + FTEDataManager manager = new FTEDataManager(this.fteYear, this.employeeMonth, this.employee.Id, false); + manager.loadEmployeeTime(); + manager.loadTemplateTime(); + FTEEmployeeTime emplTime = manager.assignedMap.get(this.selectedFteContract); + if (emplTime == null || emplTime.templateArray[this.employeeMonth - 1] == -1) { // we want use this method only when no template was added + manager.addTime(this.selectedFteContract, value * 8); + } else { + manager.upsertTimeTemplate(this.selectedFteContract, emplTime.templateArray[this.employeeMonth - 1] + value, this.employeeMonth, false); + manager.setTime(this.selectedFteContract, (emplTime.templateArray[this.employeeMonth - 1]) * 8); + } + initFteEmployeeView(); } - this.fteTimeManager.moveTimeToUnassigned(hoursToUnassign, this.employeeMonth + 1, this.contractId); - moveHoursInFTEWorkCard((-1) * fteDaysdecimal, this.employeeMonth + 1, this.employee.Id); - } catch (Exception e) { - this.assignViewError = true; - ApexPages.addMessage(new ApexPages.message(ApexPages.severity.ERROR, 'Unexpected error: ' + e.getMessage())); } - - initFteEmployeeView(); } - public void moveTimeFromUnassigned() { - if (notValidDays) { + public void unassignTime() { + loadWorkCardJobStatus(); + if (workCardJobStatus.isRunning == true) { + ApexPages.addMessage(new ApexPages.message(ApexPages.severity.ERROR, 'Cannot unassign time, FTE Tracker is currently busy.')); return; } - this.assignViewError = false; - if (this.employeeMonth == null) { - ApexPages.addMessage(new ApexPages.message(ApexPages.severity.ERROR, 'Cannot load month number')); - this.assignViewError = true; + if (!FTETrackerHelper.isContractFTE(this.contractId)) { + ApexPages.addMessage(new ApexPages.message(ApexPages.severity.ERROR, 'Contract is not in FTE Tracker, try refresh page.')); return; } - - Decimal fteDaysdecimal = this.fteDays != null && this.fteDays != '' ? Decimal.valueOf(this.fteDays) : 0; - if (this.fteDaysMax < fteDaysdecimal || fteDaysdecimal < 0) { - ApexPages.addMessage(new ApexPages.message(ApexPages.severity.ERROR, 'Too much hours to assign / hours cannot be negative')); - this.assignViewError = true; - return; + if (this.fteDays != null && this.fteDays != '') { + Decimal value = Decimal.valueOf(this.fteDays); + if (value > 0) { + FTEDataManager manager = new FTEDataManager(this.fteYear, this.employeeMonth, this.employee.Id, false); + manager.loadEmployeeTime(); + manager.loadTemplateTime(); + + FTEEmployeeTime emplTime = manager.assignedMap.get(this.selectedFteContract); + if (emplTime == null || emplTime.templateArray[this.employeeMonth - 1] == -1) { // we want use this method only when no template was added + manager.removeTime(this.contractId, value * 8); + } + initFteEmployeeView(); + } } + } - if (String.isEmpty(this.selectedFteContract)) { - ApexPages.addMessage(new ApexPages.message(ApexPages.severity.ERROR, 'Cannot find FTE Contract')); - this.assignViewError = true; - return; + public override PageReference goToEmployeeListView () { + PageReference pageRef = Page.FTE_Employee_List_View; + if (Date.today().year() != this.fteYear) { + pageref.getParameters().put('fteYear', String.valueOf(this.fteYear)); } + return pageRef; + } - if (fteDaysdecimal == 0) { - ApexPages.addMessage(new ApexPages.message(ApexPages.severity.ERROR, 'Cannot move 0 hours')); - this.assignViewError = true; - return; - } + public PageReference goToIndividualProjectView() { + PageReference pageRef = Page.FTE_Individual_Project_View; + pageref.getParameters().put('contractId', this.contractId); + pageref.getParameters().put('fteYear', String.valueOf(this.fteYear)); + return pageref; + } - try { - Decimal hoursToAssign = fteDaysdecimal * 8; - if (this.fteHoursMax < hoursToAssign) { - hoursToAssign = this.fteHoursMax; - } - this.fteTimeManager.moveTimeToAssigned(hoursToAssign, this.employeeMonth + 1, this.selectedFteContract); - moveHoursInFTEWorkCard(fteDaysdecimal, this.employeeMonth + 1, this.employee.Id); - } catch (Exception e) { - this.assignViewError = true; - ApexPages.addMessage(new ApexPages.message(ApexPages.severity.ERROR, 'Unexpected error: ' + e.getMessage() + ' Line : ' + e.getLineNumber())); - } - initFteEmployeeView(); + public PageReference goToTimeCardView() { + PageReference pageRef = null; + pageRef = Page.FTE_Time_Card_View; + pageref.getParameters().put('employeeId', this.employeeId); + pageref.getParameters().put('fteYear', String.valueOf(this.fteYear)); + pageref.getParameters().put('month', String.valueOf(this.exportMonth)); + return pageRef; } private void buildUITableData() { this.contractsTime = new List(); this.contractsTime = this.fteTimeManager.assignedMap.values(); + this.contractsTime.sort(); this.totalAssignedDays = new FTEEmployeeTime('Total Assigned Days', null); this.totalDaysWorked = new FTEEmployeeTime('Total Days Worked', null); + generateStyles(); + this.contractsTime.add(this.fteTimeManager.unassigned); this.contractsTime.add(this.totalAssignedDays); this.contractsTime.add(this.totalDaysWorked); } private void generateStyles() { - // Calculate totals and labor cost + // Calculate totals for (FTEEmployeeTime empT : this.contractsTime) { - empT.calculateDays(); - this.totalAssignedDays.sumHours(empT); - this.totalDaysWorked.sumHours(empT); + empT.calculateDaysAndTotal(); + this.totalAssignedDays.mergeEmployeeTime(empT); + this.totalDaysWorked.mergeEmployeeTime(empT); } - this.fteTimeManager.unassigned.calculateDays(); - this.totalAssignedDays.calculateDays(); + this.totalDaysWorked.mergeEmployeeTime(this.fteTimeManager.unassigned); + this.fteTimeManager.unassigned.calculateDaysAndTotal(); + this.totalAssignedDays.calculateDaysAndTotal(); + this.totalDaysWorked.calculateDaysAndTotal(); // We add css classes here to avoid complex if sections in visualgforce page for (Integer i = 0; i < 12; i++) { if (this.totalAssignedDays.daysArray[i] > 21) { - this.totalAssignedDays.cssStyle[i] = 'topTotal overbilled'; + this.totalAssignedDays.cssStyle[i] = this.totalAssignedDays.cssStyle[i].remove('fteCell') + ' topTotal overbilled'; } else { - this.totalAssignedDays.cssStyle[i] = 'topTotal'; + this.totalAssignedDays.cssStyle[i] = this.totalAssignedDays.cssStyle[i].remove('fteCell') + ' topTotal'; } - this.totalDaysWorked.cssStyle[i] = ''; + this.totalDaysWorked.cssStyle[i] = this.totalDaysWorked.cssStyle[i].remove('fteCell'); } if (totalAssignedDays.daysArray[12] > employeeNetworkDays) { - this.totalAssignedDays.cssStyle[12] = 'topTotal overbilled'; + this.totalAssignedDays.cssStyle[12] = this.totalAssignedDays.cssStyle[12] + ' topTotal overbilled'; } else { - this.totalAssignedDays.cssStyle[12] = 'topTotal'; + this.totalAssignedDays.cssStyle[12] = this.totalAssignedDays.cssStyle[12] + ' topTotal'; } - this.totalDaysWorked.sumHours(this.fteTimeManager.unassigned); - this.totalDaysWorked.calculateDays(); this.totalDaysWorked.nameCss = ''; this.totalAssignedDays.nameCss = 'topTotal'; this.fteTimeManager.unassigned.nameCss = ''; } - - private void moveHoursInFTEWorkCard(Decimal daysValue, Integer month, Id EmployeeId) { - List workCards = [SELECT Id, Month_1__c, Month_2__c, Month_3__c, Month_4__c, Month_5__c, Month_6__c, Month_7__c, Month_8__c, Month_9__c, Month_10__c, Month_11__c, - Month_12__c, Total__c, Total_Hours__c FROM FTE_Work_Card__c WHERE Employee__c =: EmployeeId]; - if (workCards.size() > 0) { - Decimal totalValue = workCards.get(0).Total__c; - Decimal totalHoursValue = workCards.get(0).Total_Hours__c; - SObject workCard = workCards.get(0); - Decimal monthValue = (Decimal) workCard.get('Month_' + month + '__c'); - - workCard.put('Total__c', TotalValue + daysValue); - workCard.put('Total_Hours__c', totalHoursValue + (daysValue * 8)); - workCard.put('Month_' + month + '__c', monthValue + daysValue); - update workCard; - } - } } \ No newline at end of file diff --git a/src/classes/FTEEmployeeMonthWrapper.cls b/src/classes/FTEEmployeeMonthWrapper.cls new file mode 100644 index 00000000..b0c1eece --- /dev/null +++ b/src/classes/FTEEmployeeMonthWrapper.cls @@ -0,0 +1,47 @@ +public class FTEEmployeeMonthWrapper implements Comparable { + + private Integer month; + private Integer year; + private Id employeeId; + + public FTEEmployeeMonthWrapper(Id employeeId, Integer month, Integer year) { + this.employeeId = employeeId; + this.month = month; + this.year = year; + } + + public Id getEmployeeId() { + return this.employeeId; + } + + public Integer getMonth() { + return this.month; + } + + public Integer getYear() { + return this.year; + } + + public Boolean equals(Object obj) { + if (obj instanceof FTEEmployeeMonthWrapper) { + FTEEmployeeMonthWrapper toCompare = (FTEEmployeeMonthWrapper) obj; + if (this.employeeId == toCompare.getEmployeeId() + && this.month == toCompare.getMonth() + && this.year == toCompare.getYear()) { + return true; + } + } + return false; + } + + public Integer hashCode() { + return System.hashCode(String.valueOf(this.employeeId) + '-' + this.month + '-' + this.year); + } + + public Integer compareTo(Object objToCompare) { + String empl1 = String.valueOf(this.employeeId) + '-' + this.year; + FTEEmployeeMonthWrapper empWrapper = ((FTEEmployeeMonthWrapper) objToCompare); + String empl2 = String.valueOf(empWrapper.getEmployeeId() + '-' + empWrapper.getYear()); + return empl1.compareTo(empl2); + } +} \ No newline at end of file diff --git a/src/classes/FTEEmployeeMonthWrapper.cls-meta.xml b/src/classes/FTEEmployeeMonthWrapper.cls-meta.xml new file mode 100644 index 00000000..cbddff8c --- /dev/null +++ b/src/classes/FTEEmployeeMonthWrapper.cls-meta.xml @@ -0,0 +1,5 @@ + + + 38.0 + Active + diff --git a/src/classes/FTEEmployeeTime.cls b/src/classes/FTEEmployeeTime.cls index 082f4503..1730eaff 100644 --- a/src/classes/FTEEmployeeTime.cls +++ b/src/classes/FTEEmployeeTime.cls @@ -1,34 +1,114 @@ /** * Helper class, used to help create correct tables in FTE UI. */ -public class FTEEmployeeTime { +public class FTEEmployeeTime implements Comparable { - public String name { get; set;} - public Id objId { get; set;} - public List hoursArray { get; set;} - public List daysArray { get; set;} - public List cssStyle { get; set;} - public String nameCss { get; set;} + public String name { get; set; } + public Boolean merged { get; set; } + public Id objId { get; set; } + public List hoursArray { get; set; } + public List daysArray { get; set; } + public List templateArray { get; set; } + + public List cssStyle { get; set; } + public String nameCss { get; set; } public FTEEmployeeTime (String name, Id objId) { + this.merged = false; this.name = name; this.objId = objId; - this.hoursArray = new Decimal [] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - this.daysArray = new Decimal [] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + this.hoursArray = new Decimal [] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + this.daysArray = new Decimal [] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + this.templateArray = new Decimal [] { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; // Uploaded csv template array, used to merge data an show on UI time projection this.cssStyle = new String [] {'fteCell', 'fteCell', 'fteCell', 'fteCell', 'fteCell', 'fteCell', 'fteCell', 'fteCell', 'fteCell', 'fteCell', - 'fteCell', 'fteCell', '', ''}; + 'fteCell', 'fteCell', '', '' }; this.nameCss = 'fteProjectCell'; } - public void calculateDays() { - for (Integer i = 0 ; i < this.hoursArray.size(); i++) { - this.daysArray[i] = FTETrackerHelper.roundtoDays(this.hoursArray[i]); + public void calculateDaysAndTotal() { + Decimal totalValue = 0; + if (this.merged == false) { + for (Integer i = 0; i < 12; i++) { + Decimal templateValue = (this.templateArray[i] != -1 ? this.templateArray[i] * 8 : -1); + Decimal hoursValue = this.hoursArray[i]; + Decimal finalValue = 0; + + if (templateValue == -1) { + finalValue = hoursValue; + } else { + if (templateValue > hoursValue) { + finalValue = templateValue; + this.cssStyle[i] += ' future'; + } else { + finalValue = hoursValue; + this.cssStyle[i] += ' blocked'; + } + } + + this.daysArray[i] = FTETrackerHelper.roundtoDays(finalValue); + totalValue += finalValue; + } + } else { + for (Integer i = 0; i < 12; i++) { // in merge method css and total was already calculated + this.daysArray[i] = FTETrackerHelper.roundtoDays(this.hoursArray[i]); + totalValue += this.hoursArray[i]; + } + } + this.daysArray[12] = FTETrackerHelper.roundtoDays(totalValue); + } + + public void mergeEmployeeTime(FTEEmployeeTime empTime) { + for (Integer i = 0; i < 12; i++) { + Decimal template1Hours = 8; + Decimal template2Hours = 8; + Decimal templateValue1 = (this.templateArray[i] != -1 ? this.templateArray[i] * (this.merged == true ? 1 : 8): -1); + Decimal templateValue2 = (empTime.templateArray[i] != -1 ? empTime.templateArray[i] * (empTime.merged == true ? 1 : 8): -1); + Decimal hoursValue1 = this.hoursArray[i]; + Decimal hoursValue2 = empTime.hoursArray[i]; + + setStyle(i, hoursValue1, templateValue1); + setStyle(i, hoursValue2, templateValue2); + + if (templateValue1 > -1 && templateValue2 > -1) { + + this.hoursArray[i] = templateValue1 + templateValue2; + this.templateArray[i] = templateValue1 + templateValue2; + + } else if (templateValue1 == -1 && templateValue2 > -1) { + + this.hoursArray[i] = hoursValue1 + templateValue2; + this.templateArray[i] = hoursValue1 + templateValue2; + + } else if (templateValue2 == -1 && templateValue1 > -1) { + + this.hoursArray[i] = templateValue1 + hoursValue2; + this.templateArray[i] = templateValue1 + hoursValue2; + + } else { + + this.hoursArray[i] += hoursValue2; + + } } + this.merged = true; } - public void sumHours(FTEEmployeeTime empTime) { - for (Integer i = 0 ; i < this.hoursArray.size(); i++) { - this.hoursArray[i] += empTime.hoursArray[i]; + private void setStyle(Integer index, Decimal value, Decimal template) { + if (template != -1) { + if (template > value) { + if (!this.cssStyle[index].contains('future')) { + this.cssStyle[index ] = 'fteCell future'; + } + } else { + if (!this.cssStyle[index].contains('future')) { + this.cssStyle[index] = 'fteCell blocked'; + } + } } } + + public Integer compareTo(Object objToCompare) { + FTEEmployeeTime emplTime = ((FTEEmployeeTime) objToCompare); + return name.compareTo(emplTime.name); + } } \ No newline at end of file diff --git a/src/classes/FTEEmployeeWrapper.cls b/src/classes/FTEEmployeeWrapper.cls new file mode 100644 index 00000000..974693b9 --- /dev/null +++ b/src/classes/FTEEmployeeWrapper.cls @@ -0,0 +1,25 @@ +public class FTEEmployeeWrapper { + + public Id employeeId { get; set; } + public Integer year { get; set; } + + public FTEEmployeeWrapper(Id employeeId, Integer year) { + this.employeeId = employeeId; + this.year = year; + } + + public Integer hashCode() { + return System.hashCode(String.valueOf(this.employeeId) + '-' + this.year); + } + + public Boolean equals(Object obj) { + if (obj instanceof FTEEmployeeWrapper) { + FTEEmployeeWrapper toCompare = (FTEEmployeeWrapper) obj; + if (this.employeeId == toCompare.employeeId + && this.year == toCompare.year) { + return true; + } + } + return false; + } +} \ No newline at end of file diff --git a/src/classes/FTEEmployeeWrapper.cls-meta.xml b/src/classes/FTEEmployeeWrapper.cls-meta.xml new file mode 100644 index 00000000..cbddff8c --- /dev/null +++ b/src/classes/FTEEmployeeWrapper.cls-meta.xml @@ -0,0 +1,5 @@ + + + 38.0 + Active + diff --git a/src/classes/FTEGenerateEmployeesWorkCardBatch.cls b/src/classes/FTEGenerateEmployeesWorkCardBatch.cls index 52c39af8..1cebca1c 100644 --- a/src/classes/FTEGenerateEmployeesWorkCardBatch.cls +++ b/src/classes/FTEGenerateEmployeesWorkCardBatch.cls @@ -1,65 +1,33 @@ /** * Batch job for createing FTE Work Cards for FTE Employee List View. */ -public without sharing class FTEGenerateEmployeesWorkCardBatch implements Database.Batchable, Database.Stateful { +public without sharing class FTEGenerateEmployeesWorkCardBatch implements Database.Batchable, Database.Stateful { private Map> workCardsMap; - private Integer calculationYear; - private Set contractsId; - private Set employeesId; + private Set dataSet; public FTEGenerateEmployeesWorkCardBatch() { this(null); } - public FTEGenerateEmployeesWorkCardBatch(Integer year) { - this(new Set(), new Set(), year); + public FTEGenerateEmployeesWorkCardBatch(Set dataSet) { + this.dataSet = dataSet; } - public FTEGenerateEmployeesWorkCardBatch(Set emplSet, Set contractSet, Integer year) { - this.employeesId = emplSet; - this.contractsId = contractSet; - this.calculationYear = year; - this.workCardsMap = new Map>(); - } - - public List start(Database.BatchableContext BC) { - List result = new List(); + public List start(Database.BatchableContext BC) { + List result = new List(); try { - - if (this.calculationYear == null) { + if (this.dataSet == null || this.dataSet.size() == 0) { result = generateWrapperList([SELECT Id, Name, Hire_Date__c, Termination_Date__c, Employee_Type__c, Employee_Status__c FROM SFDC_Employee__c ORDER BY Name]); List oldCards = [SELECT Id FROM FTE_Work_Card__c]; if (oldCards.size() > 0) { delete oldCards; } - } else if (this.calculationYear != null) { - Integer currentYear = Date.today().year(); - if (this.contractsId.size() > 0) { - List contractTimeCards = [SELECT Id, Employee__c FROM Time_Card__c WHERE - (Client__c IN: this.contractsId OR FTE_Contract__c IN: this.contractsId) - AND CALENDAR_YEAR(Date__c) =: this.calculationYear - AND (Employee__r.Employee_Type__c = 'Employee' OR Employee__r.Employee_Type__c = 'Contractor')]; - for (Time_Card__c tc : contractTimeCards) { - if (!this.employeesId.contains(tc.Employee__c)) { - this.employeesId.add(tc.Employee__c); - } - } - } - - List emplList = null; - if (this.employeesId.size() > 0) { - emplList = [SELECT Id, Name, Hire_Date__c, Termination_Date__c, Employee_Type__c, Employee_Status__c - FROM SFDC_Employee__c WHERE Id IN: this.employeesId AND (Employee_Type__c = 'Employee' OR Employee_Type__c = 'Contractor') ORDER BY Name]; - } else { - emplList = [SELECT Id, Name, Hire_Date__c, Termination_Date__c, Employee_Type__c, Employee_Status__c - FROM SFDC_Employee__c WHERE (Employee_Type__c = 'Employee' OR Employee_Type__c = 'Contractor') ORDER BY Name]; - } - - for (SFDC_Employee__c emplRec : emplList) { - result.add(new EmployeeWrapper(emplRec.Id, currentYear)); + } else if (this.dataSet != null && this.dataSet.size() > 0) { + for (FTE_Work_Card__c emplWorkCard : [SELECT Employee__c, Year__c FROM FTE_Work_Card__c WHERE Id IN: this.dataSet]) { + result.add(new FTEEmployeeWrapper(emplWorkCard.Employee__c, Integer.valueOf(emplWorkCard.Year__c))); } } @@ -72,8 +40,8 @@ public without sharing class FTEGenerateEmployeesWorkCardBatch implements Databa return result; } - public void execute(Database.BatchableContext BC, List scope) { - for (EmployeeWrapper employee : scope) { + public void execute(Database.BatchableContext BC, List scope) { + for (FTEEmployeeWrapper employee : scope) { List employeeWorkCards = [SELECT Id FROM FTE_Work_Card__c WHERE Employee__c =: employee.employeeId AND Year__c =: employee.year]; FTE_Work_Card__c employeeWorkCard = new FTE_Work_Card__c(Employee__c = employee.employeeId, Year__c = employee.year, Month_1__c = 0, Month_2__c = 0, Month_3__c = 0, Month_4__c = 0, Month_5__c = 0, Month_6__c = 0, @@ -82,57 +50,57 @@ public without sharing class FTEGenerateEmployeesWorkCardBatch implements Databa if (employeeWorkCards.size() > 0) { employeeWorkCard.Id = employeeWorkCards.get(0).Id; } - SObject sObj = (SObject) employeeWorkCard; - Decimal totalHours = 0; - Decimal monthHours = 0; - Decimal monthDays = 0; - Integer month = 1; - List employeeYearTimeCards = [SELECT Id, Total__c, Date__c, Client__r.FTE_Tracker__c, FTE_hours__c, FTE_Contract__c, - FTE_Contract__r.FTE_Tracker__c FROM Time_Card__c - WHERE Employee__c =: employee.employeeId AND CALENDAR_YEAR(Date__c) =: employee.year AND (Client__r.FTE_Tracker__c = 'Yes' - OR (Client__r.FTE_Tracker__c != 'Yes' AND FTE_Contract__r.FTE_Tracker__c = 'Yes')) - AND Total__c != null ORDER BY Date__c]; // we need only fetch timcards assigned and unassigned which are moving time to assigned - - for (Time_Card__c tc : employeeYearTimeCards) { - System.debug('TC Date: ' + tc.Date__c); - System.debug('TC Time: ' + tc.Date__c); - if (tc.Date__c.month() != month) { - monthDays = FTETrackerHelper.roundtoDays(monthHours); - sObj.put('Month_' + month + '__c', monthDays); - month = tc.Date__c.month(); - monthHours = 0; - } - Decimal realHours = tc.Total__c; + Map contractAllocationMapping = new Map(); + for (FTE_Data_Record__c futureTemplate : [SELECT Contract__c, Employee__c, Month_1__c, Month_2__c, Month_3__c, Month_4__c, Month_5__c, Month_6__c, + Month_7__c, Month_8__c, Month_9__c, Month_10__c, Month_11__c, Month_12__c + FROM FTE_Data_Record__c + WHERE Employee__c =: employee.employeeId AND Year__c =: employee.year AND Contract__r.FTE_Tracker__c = 'Yes']) { + contractAllocationMapping.put(futureTemplate.Contract__c, futureTemplate); + } - if (tc.Client__r.FTE_Tracker__c == 'Yes' && tc.FTE_Contract__c != null && tc.FTE_Contract__r.FTE_Tracker__c != 'Yes') { // We must substract hours if moved to unassigned - realHours = realHours - tc.FTE_hours__c; - } else if (tc.Client__r.FTE_Tracker__c != 'Yes' && tc.FTE_Contract__c != null && tc.FTE_Contract__r.FTE_Tracker__c == 'Yes') { // we must move hours from unassigned - realHours = tc.FTE_hours__c; + FTEDataManager dataManager = new FTEDataManager(employee.year, employee.employeeId, true); + dataManager.loadEmployeeTime(); + dataManager.loadTemplateTime(); + + Decimal totalValue = 0; + Decimal[] timeArray = new Decimal [] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + for (Id contractId : dataManager.assignedMap.keySet()) { + FTEEmployeeTime emplTime = dataManager.assignedMap.get(contractId); + for (Integer month = 1; month <= 12; month++) { + Decimal templateTime = (emplTime.templateArray[month - 1] != -1 ? emplTime.templateArray[month - 1] * 8 : -1); + Decimal loggedTime = emplTime.hoursArray[month - 1]; + Decimal finalResult = 0; + + if (templateTime != -1 && templateTime > loggedTime) { + finalResult = templateTime; + sObj.put(FTETrackerHelper.getFieldTemplateFutureName(month), true); // UI metadata, increase performance + } else if (templateTime != -1 && templateTime == loggedTime) { + finalResult = loggedTime; + sObj.put(FTETrackerHelper.getFieldTemplateBlockedName(month), true); // UI metadata increase performance + } else { + finalResult = loggedTime; + } + timeArray[month - 1] += finalResult; + totalValue += finalResult; } - - monthHours += realHours; - totalHours += realHours; } - - monthDays = FTETrackerHelper.roundtoDays(monthHours); - sObj.put('Month_' + month + '__c', monthDays); - - employeeWorkCard = (FTE_Work_Card__c) sObj; - employeeWorkCard.Total_Hours__c = totalHours; - employeeWorkCard.Total__c = FTETrackerHelper.roundtoDays(totalHours); - + employeeWorkCard.Total_Hours__c = totalValue; + employeeWorkCard.Total__c = FTETrackerHelper.roundtoDays(totalValue); + // Merge temlate data and real data + for (Integer month = 1; month <= 12; month++) { + sObj.put(FTETrackerHelper.getFieldName(month), FTETrackerHelper.roundtoDays(timeArray[month - 1])); + } upsert employeeWorkCard; } } public void finish(Database.BatchableContext BC) { - } - private List generateWrapperList(List employees) { - List result = new List(); + private List generateWrapperList(List employees) { + List result = new List(); Integer currentYear = Date.today().year(); Integer twoYearsAhead = currentYear + 2; Integer nextYear = currentYear + 1; @@ -141,36 +109,25 @@ public without sharing class FTEGenerateEmployeesWorkCardBatch implements Databa for (SFDC_Employee__c emplRec : employees) { if (emplRec.Employee_Status__c == 'Active' && (emplRec.Hire_Date__c != null && emplRec.Hire_Date__c.year() <= twoYearsAhead) && emplRec.Termination_Date__c == null) { - result.add(new EmployeeWrapper(emplRec.Id, twoYearsAhead)); + result.add(new FTEEmployeeWrapper(emplRec.Id, twoYearsAhead)); } if (emplRec.Employee_Status__c == 'Active' && (emplRec.Hire_Date__c != null && emplRec.Hire_Date__c.year() <= nextYear) && emplRec.Termination_Date__c == null) { - result.add(new EmployeeWrapper(emplRec.Id, nextYear)); + result.add(new FTEEmployeeWrapper(emplRec.Id, nextYear)); } if ((emplRec.Employee_Status__c == 'Active' && (emplRec.Hire_Date__c != null && emplRec.Hire_Date__c.year() <= currentYear)) || (emplRec.Termination_Date__c != null && emplRec.Termination_Date__c.year() == currentYear)) { - result.add(new EmployeeWrapper(emplRec.Id, currentYear)); + result.add(new FTEEmployeeWrapper(emplRec.Id, currentYear)); } if ((emplRec.Hire_Date__c != null && emplRec.Hire_Date__c.year() <= oneYearAgo) || (emplRec.Termination_Date__c != null && emplRec.Termination_Date__c.year() == oneYearAgo)) { - result.add(new EmployeeWrapper(emplRec.Id, oneYearAgo)); + result.add(new FTEEmployeeWrapper(emplRec.Id, oneYearAgo)); } } return result; } - - public class EmployeeWrapper { - - public Id employeeId { get; set; } - public Integer year { get; set; } - - public EmployeeWrapper(Id employeeId, Integer year) { - this.employeeId = employeeId; - this.year = year; - } - } } \ No newline at end of file diff --git a/src/classes/FTEGenerateEmployeesWorkCardScheduler.cls b/src/classes/FTEGenerateEmployeesWorkCardScheduler.cls index 5bdb4960..8c5ba510 100644 --- a/src/classes/FTEGenerateEmployeesWorkCardScheduler.cls +++ b/src/classes/FTEGenerateEmployeesWorkCardScheduler.cls @@ -1,5 +1,6 @@ /** - * Schedules FTEUpdateTagsBatch job, then FTERemoveNegativeHoursBatch, FTEGenerateEmployeesWorkCardBatch. + * Schedules FTEUpdateTagsBatch job, then FTETimeAllocator and FTEGenerateEmployeesWorkCardBatch. First job checks situation in which FTE Tag was deleted by FB sync, + * Second job is allocationg FTE Time by using uploaded CSV templates, the last job is calculationg work cards used by FTE Tracker Ui to increase performance. */ public without sharing class FTEGenerateEmployeesWorkCardScheduler implements Schedulable { diff --git a/src/classes/FTEHoursUploadBatch.cls b/src/classes/FTEHoursUploadBatch.cls index 5c56add8..f29d84be 100644 --- a/src/classes/FTEHoursUploadBatch.cls +++ b/src/classes/FTEHoursUploadBatch.cls @@ -1,87 +1,4 @@ -public without sharing class FTEHoursUploadBatch implements Database.Batchable, Database.Stateful { - - private Integer batchYear; - private Set employeesSet; - private Set contractSet; - private List timeData; - - public FTEHoursUploadBatch(List timeData) { - this.timeData = timeData; - } - - public List start(Database.BatchableContext BC) { - this.employeesSet = new Set(); - this.contractSet = new Set(); - - if (this.timeData.size() > 0) { - DContract__c fteContract = [SELECT Id, Name, Skip_FTE_Tracker_Trigger__c, FTE_Tracker__c FROM DContract__c WHERE Id =: this.timeData.get(0).getContractId() LIMIT 1]; - if (fteContract.FTE_Tracker__c != 'Yes') { // we must move contract to FTE - fteContract.FTE_Tracker__c = 'Yes'; - fteContract.Skip_FTE_Tracker_Trigger__c = true; - update fteContract; - contractSet.add(fteContract.Id); - } - } - - return this.timeData; - } - - public void execute(Database.BatchableContext BC, List scope) { - for (FTEUploadData fteDataRec : scope) { - this.batchYear = fteDataRec.getFTEYear(); - SFDC_Employee__c employee = [SELECT Id, Name, Hire_Date__c FROM SFDC_Employee__c WHERE Id =: fteDataRec.getEmployeeId() LIMIT 1]; - FTETimeManager fteTimeManager = new FTETimeManager(employee, this.batchYear); - fteTimeManager.loadEmployeeTime(); - - this.employeesSet.add(employee.Id); - for (Integer i = 1; i <= 12; i++) { - Decimal monthDays = fteDataRec.getMonthTime(i); - - if (monthDays != null && monthDays >= 0) { - try { - FTEEmployeeTime emplTime = fteTimeManager.assignedMap.get(fteDataRec.getContractId()); - if (emplTime == null) { - emplTime = new FTEEmployeeTime(fteDataRec.getContractName(), fteDataRec.getContractId()); - fteTimeManager.assignedMap.put(fteDataRec.getContractId(), emplTime); - } - - Decimal timeDiff = emplTime.hoursArray[i - 1] - (8 * (monthDays)); - Decimal missingTime = 0; - if (timeDiff > 0) { // we must move time to unassigned - fteTimeManager.moveTimeToUnassigned(timeDiff, i, fteDataRec.getContractId()); - } else if (timeDiff < 0) { // we must move time to assigned - missingTime = fteTimeManager.moveTimeToAssigned((-1) * timeDiff, i, fteDataRec.getContractId()); - } - - if (missingTime > 0) { - insert new FTE_Data_Record_Status__c(Status__c = 'Insufficient hours', Status_Message__c = 'There was no enough hours to move from unassigned for month : ' + i + '. Contract: ' - + fteDataRec.getContractName() + ' Employee: ' + fteDataRec.getEmployeeName() + ' hours missing : ' + missingTime.setScale(2)); - } - } catch (Exception e) { - insert new FTE_Data_Record_Status__c(Status__c = 'Other', Status_Message__c = e.getMessage() - + ' Contract : ' + fteDataRec.getContractName() + ' Employee: ' + fteDataRec.getEmployeeName()); - } - } - } - } - } - - public void finish(Database.BatchableContext BC) { - List result = [SELECT Year__c, Year_Text__c, Month_1__c, Month_2__c, Month_3__c, Month_4__c, Month_5__c, Month_6__c, Month_7__c, Month_8__c, Month_9__c, - Month_10__c, Month_11__c, Month_12__c, Message__c, Employee__c, Employee__r.Name, Processed__c, Contract__c, Contract__r.Name, Line_Number__c - FROM FTE_Data_Record__c ORDER BY Line_Number__c]; - - if (result.size() > 0) { - delete result; - } - - if (!Test.isRunningTest()) { - if (this.contractSet.size() > 0) { - Database.executeBatch(new FTEGenerateEmployeesWorkCardBatch(), 1); // we want refresh all 2 years because we can exced transaction limit in SF - // to get empl list : 3 years time cards for contract can be bigger then 50k records - } else if (this.batchYear >= Date.today().year() - 2) { - Database.executeBatch(new FTEGenerateEmployeesWorkCardBatch(this.employeesSet, new Set(), this.batchYear), 1); // refresh only employees and uploaded year - } - } - } -} \ No newline at end of file +/** + * TODO : delete, class will not be needed again + */ +public without sharing class FTEHoursUploadBatch {} \ No newline at end of file diff --git a/src/classes/FTEIndividualProjectController.cls b/src/classes/FTEIndividualProjectController.cls index 8089ad09..feaaafd2 100644 --- a/src/classes/FTEIndividualProjectController.cls +++ b/src/classes/FTEIndividualProjectController.cls @@ -48,6 +48,7 @@ public class FTEIndividualProjectController extends FTEController { tmpHelper = projectTimeMap.get(timeCard.Employee__c); } else { tmpHelper = new FTEEmployeeTime(timeCard.Employee__r.Name, timeCard.Employee__c); + projectTimeMap.put(timeCard.Employee__c, tmpHelper); } Decimal realHours = timeCard.Total__c != null ? timeCard.Total__c : 0; @@ -59,23 +60,57 @@ public class FTEIndividualProjectController extends FTEController { Integer monthIndex = timeCard.Date__c.month() - 1; tmpHelper.hoursArray[monthIndex] += realHours; - tmpHelper.hoursArray[12] += realHours; - projectTimeMap.put(timeCard.Employee__c, tmpHelper); + } + + Map templatesMap = new Map(); + for (FTE_Data_Record__c template : [SELECT Id, Employee__c, Employee__r.Name, Year__c, Month_1__c, Month_2__c, Month_3__c, Month_4__c, + Month_5__c, Month_6__c, Month_7__c, Month_8__c, Month_9__c, Month_10__c, Month_11__c, Month_12__c + FROM FTE_Data_Record__c WHERE Contract__c =: this.contractId AND Year__c =: this.fteYear]) { + FTEEmployeeTime tmpHelper; + Boolean putObject = false; + Boolean templateEmpty = true; + if (projectTimeMap.containsKey(template.Employee__c)) { + tmpHelper = projectTimeMap.get(template.Employee__c); + } else { + putObject = true; + tmpHelper = new FTEEmployeeTime(template.Employee__r.Name, template.Employee__c); + } + + SObject sobjTemplate = (SObject) template; + for (Integer month = 1; month <= 12; month++) { + Decimal monthValue = (Decimal) sobjTemplate.get(FTETrackerHelper.getFieldName(month)); + if (monthValue != null && monthValue >= 0) { + if (tmpHelper.templateArray[month - 1] == -1) { + tmpHelper.templateArray[month - 1] = 0; + } + tmpHelper.templateArray[month - 1] += monthValue; + templateEmpty = false; + } + } + if (putObject == true && templateEmpty == false) { + projectTimeMap.put(template.Employee__c, tmpHelper); + } } this.individualContractDataList = projectTimeMap.values(); for (FTEEmployeeTime empT : this.individualContractDataList) { - empT.calculateDays(); - totalProjectTime.sumHours(empT); + empT.calculateDaysAndTotal(); + totalProjectTime.mergeEmployeeTime(empT); } - totalProjectTime.calculateDays(); - totalProjectTime.cssStyle[0] = 'topTotal'; - totalProjectTime.cssStyle[1] = 'topTotal'; - totalProjectTime.cssStyle[2] = 'topTotal'; - totalProjectTime.cssStyle[3] = 'topTotal'; - totalProjectTime.cssStyle[4] = 'topTotal'; - totalProjectTime.cssStyle[5] = 'topTotal'; - totalProjectTime.cssStyle[12] = 'topTotal'; + totalProjectTime.calculateDaysAndTotal(); + totalProjectTime.cssStyle[0] += ' topTotal'; + totalProjectTime.cssStyle[1] += ' topTotal'; + totalProjectTime.cssStyle[2] += ' topTotal'; + totalProjectTime.cssStyle[3] += ' topTotal'; + totalProjectTime.cssStyle[4] += ' topTotal'; + totalProjectTime.cssStyle[5] += ' topTotal'; + totalProjectTime.cssStyle[6] += ' topTotal'; + totalProjectTime.cssStyle[7] += ' topTotal'; + totalProjectTime.cssStyle[8] += ' topTotal'; + totalProjectTime.cssStyle[9] += ' topTotal'; + totalProjectTime.cssStyle[10] += ' topTotal'; + totalProjectTime.cssStyle[11] += ' topTotal'; + totalProjectTime.cssStyle[12] += ' topTotal'; totalProjectTime.nameCss = 'topTotal'; this.individualContractDataList.add(totalProjectTime); } diff --git a/src/classes/FTEMonthTimeCard.cls b/src/classes/FTEMonthTimeCard.cls index b84965ee..4dc38489 100644 --- a/src/classes/FTEMonthTimeCard.cls +++ b/src/classes/FTEMonthTimeCard.cls @@ -3,12 +3,12 @@ public class FTEMonthTimeCard { public String name { get; set; } public Decimal[] hours { get; set; } public Decimal totalHours { get; set; } - - public FTEMonthTimeCard() {} + public Decimal templateExpectedTime { get; set; } public FTEMonthTimeCard(String name, Integer monthDays) { this.name = name; this.totalHours = 0.00; + this.templateExpectedTime = -1.0; this.hours = new Decimal[monthDays]; for (Integer i = 0; i < monthDays; i++) { this.hours[i] = 0.00; diff --git a/src/classes/FTERemoveNegativeHoursBatch.cls b/src/classes/FTERemoveNegativeHoursBatch.cls index 15f92e9b..5d8e8d07 100644 --- a/src/classes/FTERemoveNegativeHoursBatch.cls +++ b/src/classes/FTERemoveNegativeHoursBatch.cls @@ -1,8 +1,8 @@ /** * Class for adjusting hours when we will have negative values in a month after moving hours. - * TODO : probably to delete + * TODO : class to refactor, currently not used, that is why code is commented */ -public without sharing class FTERemoveNegativeHoursBatch implements Database.Batchable, Database.Stateful { +public without sharing class FTERemoveNegativeHoursBatch {} /*implements Database.Batchable, Database.Stateful { public List start(Database.BatchableContext BC) { AggregateResult[] groupedResults = [SELECT CALENDAR_MONTH(Date__c), CALENDAR_YEAR(Date__c), Employee__c, TC_Contract__c, SUM(Hours__c) @@ -143,4 +143,4 @@ public without sharing class FTERemoveNegativeHoursBatch implements Database.Bat public Decimal hours {get; set;} public Date fteDate {get; set;} } -} \ No newline at end of file +}*/ \ No newline at end of file diff --git a/src/classes/FTETimeAllocator.cls b/src/classes/FTETimeAllocator.cls new file mode 100644 index 00000000..a26607af --- /dev/null +++ b/src/classes/FTETimeAllocator.cls @@ -0,0 +1,109 @@ +public without sharing class FTETimeAllocator implements Database.Batchable, Database.Stateful { + + private Integer cacheYear; + private Id cacheEmployee; + private List cacheRecords; + private Set cacheWorkCards; + private Boolean csvUpload; + + public FTETimeAllocator(Boolean csvUpload) { + this.csvUpload = csvUpload; + this.cacheWorkCards = new Set(); + } + + public List start(Database.BatchableContext BC) { // we use data wrapper because we don't want process same month a few times, for example multiple contract updates + List result = null; + + result = [SELECT Id, Employee__c, Year__c, + Month_1__c, Month_2__c, Month_3__c, Month_4__c, Month_5__c, Month_6__c, + Month_7__c, Month_8__c, Month_9__c, Month_10__c, Month_11__c, Month_12__c, + Month_Updated_1__c, Month_Updated_2__c, Month_Updated_3__c, Month_Updated_4__c, Month_Updated_5__c, Month_Updated_6__c, + Month_Updated_7__c, Month_Updated_8__c, Month_Updated_9__c, Month_Updated_10__c, Month_Updated_11__c, Month_Updated_12__c + FROM FTE_Data_Record__c + WHERE Month_Updated_1__c = true OR Month_Updated_2__c = true OR Month_Updated_3__c = true OR Month_Updated_4__c = true OR + Month_Updated_5__c = true OR Month_Updated_6__c = true OR Month_Updated_7__c = true OR Month_Updated_8__c = true OR + Month_Updated_9__c = true OR Month_Updated_10__c = true OR Month_Updated_11__c = true OR Month_Updated_12__c = true + ORDER BY Employee__c]; + + Set emplDateSet = new Set(); + for (FTE_Data_Record__c fteData : result) { + SObject sObjTemplate = (SObject) fteData; + for (Integer month = 1; month <= 12; month++) { + Boolean needUpdate = (Boolean) sObjTemplate.get(FTETrackerHelper.getFieldUpdatedName(month)); + if (needUpdate) { + emplDateSet.add(new FTEEmployeeMonthWrapper(fteData.Employee__c, month, Integer.valueOf(fteData.Year__c))); + } + } + } + + List batchData = new List(); + batchData.addAll(emplDateSet); + batchData.sort(); // we want employees in the same order to cache DB data + + return batchData; + } + + public void execute(Database.BatchableContext BC, List scope) { + for (FTEEmployeeMonthWrapper employeeMonth : scope) { + // Handle cache reload + if (employeeMonth.getEmployeeId() != this.cacheEmployee) { + this.cacheEmployee = employeeMonth.getEmployeeId(); + this.cacheYear = employeeMonth.getYear(); + List employeeWorkCards = [SELECT Id FROM FTE_Work_Card__c WHERE Employee__c =: this.cacheEmployee AND Year__c =: this.cacheYear]; + if (employeeWorkCards.size() > 0) { + this.cacheWorkCards.add(employeeWorkCards.get(0).Id); + } + this.cacheRecords = [SELECT Id, Employee__c, Year__c, Contract__c, + Month_1__c, Month_2__c, Month_3__c, Month_4__c, Month_5__c, Month_6__c, + Month_7__c, Month_8__c, Month_9__c, Month_10__c, Month_11__c, Month_12__c, + Month_Updated_1__c, Month_Updated_2__c, Month_Updated_3__c, Month_Updated_4__c, Month_Updated_5__c, Month_Updated_6__c, + Month_Updated_7__c, Month_Updated_8__c, Month_Updated_9__c, Month_Updated_10__c, Month_Updated_11__c, Month_Updated_12__c + FROM FTE_Data_Record__c WHERE Employee__c =: this.cacheEmployee AND Year__c =: this.cacheYear AND Contract__r.FTE_Tracker__c = 'Yes' + ORDER BY LastModifiedDate]; + } + + // Load employee month time table + Integer month = employeeMonth.getMonth(); + FTEDataManager fteManager = new FTEDataManager(this.cacheYear, month, this.cacheEmployee, true); + fteManager.loadEmployeeTime(); + fteManager.removeNegativeTime(); + + List toAssignTemplates = new List(); // Helper list for sorting, first we must unassign time to be sure all time will be allocated if possible + List templatesInOrder = new List(); + for (FTE_Data_Record__c templateRecord : this.cacheRecords) { + SObject sObjTemplate = (SObject) templateRecord; + Decimal monthValue = (Decimal) sObjTemplate.get(FTETrackerHelper.getFieldName(month)); // get month threshold + if (fteManager.shouldUnassign(templateRecord.Contract__c, monthValue, month)) { + templatesInOrder.add(templateRecord); + } else { + toAssignTemplates.add(templateRecord); + } + } + if (toAssignTemplates.size() > 0) { + templatesInOrder.addAll(toAssignTemplates); + } + + // Template time allocation loop + for (FTE_Data_Record__c template : templatesInOrder) { + SObject sObjTemplate = (SObject) template; + Decimal monthValue = (Decimal) sObjTemplate.get(FTETrackerHelper.getFieldName(month)); // get month threshold + + fteManager.setTime(template.Contract__c, monthValue * 8); // allocate time in hours not days + + sObjTemplate.put(FTETrackerHelper.getFieldUpdatedName(month), false); // rest month flag + } + + if (this.cacheRecords.size() > 0) { + update this.cacheRecords; + } + } + } + + public void finish(Database.BatchableContext BC) { + if (this.csvUpload == false && !Test.isRunningTest()) { // we want run daily update for all work cards and employees + Database.executeBatch(new FTEGenerateEmployeesWorkCardBatch(), 1); + } else if (this.csvUpload == true && this.cacheWorkCards.size() > 0 && !Test.isRunningTest()) { + Database.executeBatch(new FTEGenerateEmployeesWorkCardBatch(this.cacheWorkCards), 1); // refresh only uploaded contract and employees + } + } +} \ No newline at end of file diff --git a/src/classes/FTETimeAllocator.cls-meta.xml b/src/classes/FTETimeAllocator.cls-meta.xml new file mode 100644 index 00000000..cbddff8c --- /dev/null +++ b/src/classes/FTETimeAllocator.cls-meta.xml @@ -0,0 +1,5 @@ + + + 38.0 + Active + diff --git a/src/classes/FTETimeCardGenerator.cls b/src/classes/FTETimeCardGenerator.cls index 3570261a..197b4fbe 100644 --- a/src/classes/FTETimeCardGenerator.cls +++ b/src/classes/FTETimeCardGenerator.cls @@ -6,6 +6,7 @@ public class FTETimeCardGenerator { private Integer employeeMonth; private Id employeeId; private FTEMonthTimeCard unassignedHours; + private FTEMonthTimeCard assignedHours; private Map fteContractMap; private Map contractMap; @@ -31,14 +32,15 @@ public class FTETimeCardGenerator { this.monthYearText = DateTime.newInstance(this.fteYear, this.employeeMonth, 1).format('MMMM yyyy'); this.fileName = employee.Name.replaceAll(' ', '_') + '_' + this.monthYearText.replaceAll(' ', '_') + '.csv'; - List timeCards = [SELECT Id, Client__c, Client__r.FTE_Tracker__c, Client__r.Name, Total__c, FTE_hours__c, - FTE_Contract__c, FTE_Contract__r.Name, FTE_Contract__r.FTE_Tracker__c, Date__c + List timeCards = [SELECT Id, Client__c, Client__r.FTE_Tracker__c, Client__r.Name, Total__c, + FTE_Contract__c, Date__c FROM Time_Card__c WHERE Employee__c =: this.employeeId AND Client__c != null AND CALENDAR_YEAR(Date__c) =: this.fteYear AND CALENDAR_MONTH(Date__c) =: this.employeeMonth ORDER BY Client__r.Name]; this.monthDays = Date.daysInMonth(this.fteYear, employeeMonth); this.unassignedHours = new FTEMonthTimeCard('Overhead', this.monthDays); + this.assignedHours = new FTEMonthTimeCard('FTE assigned time', this.monthDays); this.fteContractMap = new Map(); this.contractMap = new Map(); @@ -51,6 +53,9 @@ public class FTETimeCardGenerator { } } + // Loads time templates, we need generate missing hours in th csv and UI table + loadTemplateTime(employee); + fteCards = [SELECT Id, Client__c, Client__r.FTE_Tracker__c, Client__r.Name, Total__c, FTE_hours__c, FTE_Contract__r.CreatedDate, Client__r.CreatedDate, FTE_Contract__c, FTE_Contract__r.Name, FTE_Contract__r.FTE_Tracker__c, Date__c FROM Time_Card__c WHERE Id IN: fteCards ORDER BY Client__r.FTE_Tracker__c ASC, FTE_Contract__r.CreatedDate, Client__r.CreatedDate]; @@ -63,6 +68,14 @@ public class FTETimeCardGenerator { fteCards = moveTimeCardTime(fteCards); } + for (DContract__c dContract : [SELECT Id FROM DContract__c WHERE Id IN: this.fteContractMap.keySet() ORDER BY CreatedDate]) { // we want keep some order of time generation + FTEMonthTimeCard tmpHelper = this.fteContractMap.get(dContract.Id); + Decimal missingTime = (-1) * (tmpHelper.totalHours - (tmpHelper.templateExpectedTime >= 0 ? tmpHelper.templateExpectedTime * 8 : 0)); + if (missingTime > 0) { + generateFutureTime(dContract.Id, missingTime); + } + } + // Generate list for UI and csv file return getResult(); } @@ -94,8 +107,10 @@ public class FTETimeCardGenerator { monthCard.hours[i] -= hoursToMove; monthCard.totalHours -= hoursToMove; - unassignedHours.hours[i] -= hoursToMove; - unassignedHours.totalHours -= hoursToMove; + this.assignedHours.hours[i] += hoursToMove; + this.assignedHours.totalHours += hoursToMove; + this.unassignedHours.hours[i] -= hoursToMove; + this.unassignedHours.totalHours -= hoursToMove; hoursHelper -= hoursToMove; fteCard.FTE_hours__c -= hoursToMove; @@ -127,8 +142,10 @@ public class FTETimeCardGenerator { monthCard.hours[i] -= hoursToMove; monthCard.totalHours -= hoursToMove; - unassignedHours.hours[i] += hoursToMove; - unassignedHours.totalHours += hoursToMove; + this.assignedHours.hours[i] -= hoursToMove; + this.assignedHours.totalHours -= hoursToMove; + this.unassignedHours.hours[i] += hoursToMove; + this.unassignedHours.totalHours += hoursToMove; hoursHelper -= hoursToMove; fteCard.FTE_hours__c -= hoursToMove; @@ -205,6 +222,68 @@ public class FTETimeCardGenerator { return timeMoved == true ? timeCards : new List(); } + public void generateFutureTime(Id contractId, Decimal missingTime) { + FTEMonthTimeCard monthCard = this.fteContractMap.get(contractId); + // Add time in days in which employee was working on that contract + Set contractWorkingDays = new Set(); + Boolean canAddToPrevoiusDay = false; + + for (Integer day = 0; day < this.monthDays; day++) { // add future time to days where employee was working on contract + if (monthCard.hours[day] > 0 && this.assignedHours.hours[day] < 8) { + if (FTETrackerHelper.isWorkingDay(this.fteYear, this.employeeMonth, day + 1)) { + missingTime = addFutureTime(monthCard, missingTime, day); + } + if (missingTime <= 0) { + break; + } + contractWorkingDays.add(day); + } + } + + if (missingTime > 0 && contractWorkingDays.size() > 0) { + Integer dayHelper = 0; + for (Integer day : contractWorkingDays) { // fill the nearest days + + Integer previousDay = day - 1; + Integer nextDay = day + 1; + + if (previousDay >= 0 && !contractWorkingDays.contains(previousDay) && monthCard.hours[previousDay] == 0 + && this.assignedHours.hours[previousDay] < 8 && FTETrackerHelper.isWorkingDay(this.fteYear, this.employeeMonth, previousDay + 1)) { + missingTime = addFutureTime(monthCard, missingTime, previousDay); + } + + if (missingTime > 0 && nextDay < this.monthDays && !contractWorkingDays.contains(nextDay) && monthCard.hours[nextDay] == 0 + && this.assignedHours.hours[nextDay] < 8 && FTETrackerHelper.isWorkingDay(this.fteYear, this.employeeMonth, nextDay + 1)) { + missingTime = addFutureTime(monthCard, missingTime, nextDay); + } + + if (missingTime <= 0) { + break; + } + } + } + + if (missingTime > 0) { // fill all working days < 8 + for (Integer day = 0; day < this.monthDays; day++) { + if (FTETrackerHelper.isWorkingDay(this.fteYear, this.employeeMonth, day + 1) && this.assignedHours.hours[day] < 8) { + missingTime = addFutureTime(monthCard, missingTime, day); + if (missingTime <= 0) { + break; + } + } + } + } + + Integer day = 0; + while (missingTime > 0) { // spread time to +8 if still missing, we are adding max 1 hour each day + missingTime = addFutureTimeOverEight(monthCard, missingTime, day); + day++; + if (day >= this.monthDays) { + day = 0; + } + } + } + public void generateCsv() { if (this.fileName == null) { return; @@ -223,7 +302,7 @@ public class FTETimeCardGenerator { this.csvFile += '"Month-Year:",'; this.csvFile += '"' + this.monthYearText + '"'; this.csvFile += csvEmptyRowPart2 + NEW_LINE; - this.csvFile += '"Report Type:","Hours by Project",'; + this.csvFile += '"Report Type:","Hours by Project"'; this.csvFile += csvEmptyRowPart2 + NEW_LINE; this.csvFile += csvEmptyRowPart1 + csvEmptyRowPart2 + NEW_LINE; @@ -256,6 +335,49 @@ public class FTETimeCardGenerator { this.csvFile += csvEmptyRowPart2 + NEW_LINE; } + private Decimal addFutureTime(FTEMonthTimeCard monthCard, Decimal missingTime, Integer day) { + Decimal hours = 8 - this.assignedHours.hours[day]; + hours = missingTime > hours ? hours : missingTime; + if (hours > 0) { + monthCard.hours[day] += hours; + monthCard.totalHours += hours; + this.assignedHours.hours[day] += hours; + this.assignedHours.totalHours += hours; + } + return missingTime - hours; + } + + private Decimal addFutureTimeOverEight(FTEMonthTimeCard monthCard, Decimal missingTime, Integer day) { + Decimal hours = missingTime > 1.0 ? 1.0 : missingTime; + monthCard.hours[day] += hours; + monthCard.totalHours += hours; + this.assignedHours.hours[day] += hours; + this.assignedHours.totalHours += hours; + return missingTime - hours; + } + + private void loadTemplateTime(SFDC_Employee__c employee) { + for (FTE_Data_Record__c futureTemplate : [SELECT Contract__c, Contract__r.Name, Employee__c, Month_1__c, Month_2__c, Month_3__c, Month_4__c, + Month_5__c, Month_6__c, Month_7__c, Month_8__c, Month_9__c, Month_10__c, Month_11__c, Month_12__c + FROM FTE_Data_Record__c + WHERE Employee__c =: employee.Id AND Year__c =: this.fteYear AND Contract__r.FTE_Tracker__c = 'Yes']) { + SObject templateSObj = (SObject) futureTemplate; + + Decimal monthValue = (Decimal) templateSObj.get(FTETrackerHelper.getFieldName(this.employeeMonth)); + if (monthValue == -1) { + continue; + } + + if (!this.fteContractMap.containsKey(futureTemplate.Contract__c)) { + this.fteContractMap.put(futureTemplate.Contract__c, new FTEMonthTimeCard(futureTemplate.Contract__r.Name, monthDays)); + } + + FTEMonthTimeCard tmpHelper = this.fteContractMap.get(futureTemplate.Contract__c); + tmpHelper.templateExpectedTime = monthValue; + } + } + + private void addCsvCell(String val, Integer countValue, Integer limitValue) { if (countValue != limitValue) { this.csvFile += '"' + val + '",'; @@ -271,15 +393,21 @@ public class FTETimeCardGenerator { for (Integer i = 0; i < this.monthDays; i++) { resRecord.hours[i] = resRecord.hours[i].setScale(2); } + resRecord.totalHours = resRecord.totalHours.setScale(2); } for (Integer i = 0; i < this.monthDays; i++) { unassignedHours.hours[i] = unassignedHours.hours[i].setScale(2); } + unassignedHours.totalHours = unassignedHours.totalHours.setScale(2); result.add(unassignedHours); return result; } private void addLoggedTime(Time_Card__c tc) { + if (tc.Total__c == null || tc.Total__c == 0) { // we skip tc only for now + return; + } + Integer day = tc.Date__c.day(); if (tc.Client__r.FTE_Tracker__c == 'Yes') { if (!this.fteContractMap.containsKey(tc.Client__c)) { @@ -287,6 +415,7 @@ public class FTETimeCardGenerator { } FTEMonthTimeCard monthCard = this.fteContractMap.get(tc.Client__c); setHoursValue(monthCard, day, tc.Total__c); + setHoursValue(this.assignedHours, day, tc.Total__c); this.total += tc.Total__c; } else { if (!this.contractMap.containsKey(tc.Client__c)) { // we use additional map as a helper to move hours from contract @@ -294,7 +423,7 @@ public class FTETimeCardGenerator { } FTEMonthTimeCard monthCard = this.contractMap.get(tc.Client__c); setHoursValue(monthCard, day, tc.Total__c); - setHoursValue(unassignedHours, day, tc.Total__c); + setHoursValue(this.unassignedHours, day, tc.Total__c); this.totalUnassigned += tc.Total__c; } } diff --git a/src/classes/FTETimeManager.cls b/src/classes/FTETimeManager.cls index 43fa88dd..258514f4 100644 --- a/src/classes/FTETimeManager.cls +++ b/src/classes/FTETimeManager.cls @@ -1,297 +1,5 @@ /** - * FTETimeManager for loading Employee time and managing it. + * TODO: class to delete. */ public class FTETimeManager { - - private SFDC_Employee__c employee; - private Integer fteYear; - - public Integer employeeNetworkDays { get; set;} - - public FTEEmployeeTime unassigned { get; set;} - public Map assignedMap { get; set;} - public Map unassignedMap { get; set;} - - public FTETimeManager(SFDC_Employee__c employee, Integer fteYear) { - this.employee = employee; - this.fteYear = fteYear; - - this.assignedMap = new Map(); - this.unassignedMap = new Map(); - this.unassigned = new FTEEmployeeTime('Unassigned', null); // Sum of all unassigned employee contracts - } - - public void loadEmployeeTime() { - List timeCards = [SELECT Id, Client__c, Client__r.FTE_Tracker__c, Client__r.Name, - Total__c, FTE_hours__c, FTE_Contract__c, - FTE_Contract__r.FTE_Tracker__c, FTE_Contract__r.Name, - Date__c FROM Time_Card__c - WHERE Employee__c =: this.employee.Id AND Client__c != null - AND CALENDAR_YEAR(Date__c) =: this.fteYear - ORDER BY Client__r.Name]; - - FTEEmployeeTime tmpHelper; - for (Time_Card__c timeCard : timeCards) { - calculateLoggedTime(timeCard); // sum hours and moved hours - } - } - - public void moveTimeToUnassigned(Decimal fteHours, Integer month, Id contractId) { - Date monthStart = Date.newInstance(this.fteYear, month, 1); - Date endMonth = Date.newInstance(this.fteYear, month, Date.daysInMonth(this.fteYear, month)); - - Decimal hoursToUnassign = fteHours; - Boolean stopUpdating = false; - - List timeCardsToUpdate = new List(); - List timeCardsFromDB = [SELECT Id, Client__c, Client__r.FTE_Tracker__c, Client__r.Name, - Total__c, FTE_hours__c, FTE_Contract__c, - FTE_Contract__r.FTE_Tracker__c, FTE_Contract__r.Name, - Date__c FROM Time_Card__c - WHERE Employee__c =: this.employee.Id AND Client__c != null - AND (Client__r.FTE_Tracker__c = 'No' OR Client__r.FTE_Tracker__c = '') - AND FTE_Contract__c =: contractId - AND Date__c >=: monthStart AND Date__c <=: endMonth]; - - // If we have moved hours from unassigned to assigned we want take these hours back. - for (Time_Card__c tc : timeCardsFromDB) { - Decimal movedHours = tc.FTE_Hours__c; - Decimal hours = movedHours > hoursToUnassign ? hoursToUnassign : movedHours; - - if (hours > 0) { // we try remove tag from tc - tc.FTE_hours__c = tc.FTE_hours__c - hours; - hoursToUnassign -= hours; - if (tc.FTE_hours__c <= 0) { - tc.FTE_Contract__c = null; - } - timeCardsToUpdate.add(tc); - if (hoursToUnassign <= 0) { - stopUpdating = true; - break; - } - } - } - - // If we already have tag from assigned we want take more hours from that tag, - // we need only one time card we can move hours to one contract - if (stopUpdating == false) { - timeCardsFromDB = [SELECT Id, Client__c, Client__r.FTE_Tracker__c, Client__r.Name, - Total__c, FTE_hours__c, FTE_Contract__c, - FTE_Contract__r.FTE_Tracker__c, FTE_Contract__r.Name, - Date__c FROM Time_Card__c - WHERE Employee__c =: this.employee.Id AND Client__c != null - AND Client__c =: contractId AND FTE_Contract__c != null - AND (FTE_Contract__r.FTE_Tracker__c = 'No' OR FTE_Contract__r.FTE_Tracker__c = '') - AND Date__c >=: monthStart AND Date__c <=: endMonth LIMIT 1]; - - if (timeCardsFromDB.size() > 0) { - Time_Card__c tc = timeCardsFromDB.get(0); - tc.FTE_hours__c = tc.FTE_hours__c != null ? tc.FTE_hours__c + hoursToUnassign : hoursToUnassign; - timeCardsToUpdate.add(tc); - stopUpdating = true; - } - } - - // If we don't have any tag we need add one in time cards - if (stopUpdating == false) { - timeCardsFromDB = [SELECT Id, Client__c, Client__r.FTE_Tracker__c, Client__r.Name, - Total__c, FTE_hours__c, FTE_Contract__c, - FTE_Contract__r.FTE_Tracker__c, FTE_Contract__r.Name, - Date__c FROM Time_Card__c - WHERE Employee__c =: this.employee.Id AND Client__c != null - AND Client__c =: contractId - AND FTE_Contract__c = null - AND Date__c >=: monthStart AND Date__c <=: endMonth LIMIT 1]; - - if (timeCardsFromDB.size() > 0) { - Time_Card__c tc = timeCardsFromDB.get(0); - tc.FTE_Contract__c = this.unassignedMap.values()[getContractIndex()].objId; - tc.FTE_hours__c = hoursToUnassign; - timeCardsToUpdate.add(tc); - stopUpdating = true; - } - } - - // If we don't have any empty time card we need create a empty one to move hours - if (stopUpdating == false) { - Time_Card__c tc = new Time_Card__c(Client__c = contractId, Employee__c = this.employee.Id, Date__c = monthStart, - FTE_only__c = true, Total__c = 0, FTE_hours__c = hoursToUnassign, - FTE_Contract__c = this.unassignedMap.values()[getContractIndex()].objId); - timeCardsToUpdate.add(tc); - stopUpdating = true; - } - - upsert timeCardsToUpdate; - } - - public Decimal moveTimeToAssigned(Decimal fteHours, Integer month, Id contractId) { - Date monthStart = Date.newInstance(this.fteYear, month, 1); - Date endMonth = Date.newInstance(this.fteYear, month, Date.daysInMonth(this.fteYear, month)); - - Decimal hoursToAssign = fteHours; - Boolean stopUpdating = false; - - List timeCardsToUpdate = new List(); - List timeCardsFromDB = [SELECT Id, Client__c, Client__r.FTE_Tracker__c, Client__r.Name, - Total__c, FTE_hours__c, FTE_Contract__c, - FTE_Contract__r.FTE_Tracker__c, FTE_Contract__r.Name, - Date__c FROM Time_Card__c - WHERE Employee__c =: this.employee.Id AND Client__c != null - AND Client__r.FTE_Tracker__c = 'Yes' AND FTE_Hours__c > 0 - AND Client__c =: contractId AND FTE_Contract__c != null - AND (FTE_Contract__r.FTE_Tracker__c = 'No' OR FTE_Contract__r.FTE_Tracker__c = '') - AND Date__c >=: monthStart AND Date__c <=: endMonth]; - - Set emptyContracts = new Set(); - // If we have moved hours from assigned to unassigned we want take these hours back. - for (Time_Card__c tc : timeCardsFromDB) { - Decimal freeHours = this.unassignedMap.get(tc.FTE_Contract__c).hoursArray[month - 1]; - Decimal movedHours = tc.FTE_Hours__c; - Decimal hours = movedHours > freeHours ? freeHours : movedHours; - if (hours > 0) { // we try remove tag from tc - Decimal toAssign = hoursToAssign > hours ? hours : hoursToAssign; - tc.FTE_hours__c = tc.FTE_hours__c - toAssign; - hoursToAssign -= toAssign; - this.unassignedMap.get(tc.FTE_Contract__c).hoursArray[month -1] -= toAssign; - if (tc.FTE_hours__c <= 0) { - tc.FTE_Contract__c = null; - } - timeCardsToUpdate.add(tc); - if (hoursToAssign <= 0) { - stopUpdating = true; - break; - } - } - } - - // If we already have tag from unassigned we want take more hours from that tag - if (stopUpdating == false) { - timeCardsFromDB = [SELECT Id, Client__c, Client__r.FTE_Tracker__c, Client__r.Name, - Total__c, FTE_hours__c, FTE_Contract__c, - FTE_Contract__r.FTE_Tracker__c, FTE_Contract__r.Name, - Date__c FROM Time_Card__c - WHERE Employee__c =: this.employee.Id AND Client__c != null - AND (Client__r.FTE_Tracker__c = 'No' OR Client__r.FTE_Tracker__c = '') - AND FTE_Contract__c =: contractId - AND Date__c >=: monthStart AND Date__c <=: endMonth]; - - for (Time_Card__c tc : timeCardsFromDB) { - Decimal hours = this.unassignedMap.get(tc.Client__c).hoursArray[month - 1]; - if (hours > 0) { // we try move hours to this client tag - Decimal toAssign = hoursToAssign > hours ? hours : hoursToAssign; - tc.FTE_hours__c = tc.FTE_hours__c != null ? tc.FTE_hours__c + toAssign : toAssign; - hoursToAssign -= toAssign; - this.unassignedMap.get(tc.Client__c).hoursArray[month - 1] -= toAssign; - timeCardsToUpdate.add(tc); - if (hoursToAssign <= 0) { - stopUpdating = true; - break; - } - emptyContracts.add(tc.Client__c); - } else { - emptyContracts.add(tc.Client__c); - } - } - } - - // if we still need hours we will take time cards without any tag and add tag there - if (stopUpdating == false) { - timeCardsFromDB = [SELECT Id, Client__c, Client__r.FTE_Tracker__c, Client__r.Name, - Total__c, FTE_hours__c, FTE_Contract__c, - FTE_Contract__r.FTE_Tracker__c, FTE_Contract__r.Name, - Date__c FROM Time_Card__c - WHERE Employee__c =: this.employee.Id AND Client__c != null - AND (Client__r.FTE_Tracker__c = 'No' OR Client__r.FTE_Tracker__c = '') - AND (FTE_Contract__c = null OR FTE_Contract__c = '') AND Client__c NOT IN: emptyContracts - AND Date__c >=: monthStart AND Date__c <=: endMonth]; - - for (Time_Card__c tc : timeCardsFromDB) { - Decimal hours = this.unassignedMap.get(tc.Client__c).hoursArray[month - 1]; - if (hours > 0) { - Decimal toAssign = hoursToAssign > hours ? hours : hoursToAssign; - tc.FTE_hours__c = tc.FTE_hours__c != null ? tc.FTE_hours__c + toAssign : toAssign; - tc.FTE_Contract__c = contractId; - hoursToAssign -= toAssign; - this.unassignedMap.get(tc.Client__c).hoursArray[month - 1] -= toAssign; - timeCardsToUpdate.add(tc); - if (hoursToAssign <= 0) { - stopUpdating = true; - break; - } - emptyContracts.add(tc.Client__c); - } else { - emptyContracts.add(tc.Client__c); - } - } - } - - // If we have available time we need create empty time card with tag - if (stopUpdating == false) { - for (Id conId : this.unassignedMap.keySet()) { - if (!emptyContracts.contains(contractId)) { - Decimal hours = this.unassignedMap.get(conId).hoursArray[month - 1]; - if (hours > 0) { - Decimal toAssign = hoursToAssign > hours ? hours : hoursToAssign; - Time_Card__c tc = new Time_Card__c(Client__c = conId, Employee__c = this.employee.Id, Date__c = monthStart, - FTE_only__c = true, Total__c = 0, FTE_hours__c = toAssign, - FTE_Contract__c = contractId); - hoursToAssign -= toAssign; - timeCardsToUpdate.add(tc); - if (hoursToAssign <= 0) { - stopUpdating = true; - break; - } - } - } - } - } - - upsert timeCardsToUpdate; - return hoursToAssign; - } - - private void calculateLoggedTime(Time_Card__c timeCard) { - Decimal loggedTime = (timeCard.Total__c != null ? timeCard.Total__c : 0); - Decimal movedTime = (timeCard.FTE_hours__c != null ? timeCard.FTE_hours__c : 0); - - // "Moved from" part this.contractsTime.add(this.unassigned); - sumLoggedTime(timeCard.Client__r.FTE_Tracker__c == 'Yes', timeCard.Client__c, timeCard.Client__r.Name, (loggedTime - movedTime), timeCard.Date__c.month()); - // If no FTE Tag values we don't need to process moved hours - if (timeCard.FTE_Contract__c == null || movedTime == 0) { - return; - } - // "Moved to" part - sumLoggedTime(timeCard.FTE_Contract__r.FTE_Tracker__c == 'Yes', timeCard.FTE_Contract__c, timeCard.FTE_Contract__r.Name, movedTime, timeCard.Date__c.month()); - } - - private void sumLoggedTime(Boolean fteTracker, Id clientId, String clientName, Decimal loggedHours, Integer month) { - FTEEmployeeTime tmpHelper = this.unassigned; - if (fteTracker == true) { - if (!this.assignedMap.containsKey(clientId)) { - this.assignedMap.put(clientId, new FTEEmployeeTime(clientName, clientId)); - } - tmpHelper = this.assignedMap.get(clientId); - tmpHelper.hoursArray[month - 1] += loggedHours; - tmpHelper.hoursArray[12] += loggedHours; - } else { - if (!this.unassignedMap.containsKey(clientId)) { - this.unassignedMap.put(clientId, new FTEEmployeeTime(clientName, clientId)); - } - tmpHelper.hoursArray[month - 1] += loggedHours; - tmpHelper.hoursArray[12] += loggedHours; - tmpHelper = this.unassignedMap.get(clientId); - tmpHelper.hoursArray[month - 1] += loggedHours; - tmpHelper.hoursArray[12] += loggedHours; - } - } - - private Integer getContractIndex() { - Integer upperLimit = this.unassignedMap.size(); - if (upperLimit == 0) { - ApexPages.addMessage(new ApexPages.message(ApexPages.severity.ERROR, 'Cannot find unassigned contract')); - return 0; - } - Integer rand = Math.round(Math.random()*1000); - return Math.mod(rand, upperLimit); - } } \ No newline at end of file diff --git a/src/classes/FTETrackerController.cls b/src/classes/FTETrackerController.cls index 13352d73..e2681a0a 100644 --- a/src/classes/FTETrackerController.cls +++ b/src/classes/FTETrackerController.cls @@ -47,14 +47,20 @@ public class FTETrackerController extends FTEController { public String testQueryCount { get; set; } public List getEmployeesList() { + loadWorkCardJobStatus(); this.employees = new List(); List workCards = null; QueryBuilder qb = new QueryBuilder('FTE_Work_Card__c'); QueryBuilder qbCount = new QueryBuilder('FTE_Work_Card__c'); - qb.addColumns(new List { 'Id', 'Employee__c', 'Employee__r.Hire_Date__c', 'Employee__r.Name', 'Month_1__c', 'Month_2__c', 'Month_3__c', - 'Month_4__c', 'Month_5__c', 'Month_6__c', 'Month_7__c', 'Month_8__c', 'Month_9__c', 'Month_10__c', 'Month_11__c', - 'Month_12__c', 'Total__c', 'Total_Hours__c', 'Year__c'}); + qb.addColumns(new List { 'Id', 'Employee__c', 'Employee__r.Hire_Date__c', 'Employee__r.Name', 'Total__c', + 'Year__c', 'Month_1__c', 'Month_2__c', 'Month_3__c', 'Month_4__c', 'Month_5__c', 'Month_6__c', + 'Month_7__c', 'Month_8__c', 'Month_9__c', 'Month_10__c', 'Month_11__c', 'Month_12__c', + 'Month_Future_1__c', 'Month_Future_2__c', 'Month_Future_3__c', 'Month_Future_4__c', 'Month_Future_5__c', 'Month_Future_6__c', + 'Month_Future_7__c', 'Month_Future_8__c', 'Month_Future_9__c', 'Month_Future_10__c', 'Month_Future_11__c', 'Month_Future_12__c', + 'Month_Blocked_1__c', 'Month_Blocked_2__c', 'Month_Blocked_3__c', 'Month_Blocked_4__c', 'Month_Blocked_5__c', 'Month_Blocked_6__c', + 'Month_Blocked_7__c', 'Month_Blocked_8__c', 'Month_Blocked_9__c', 'Month_Blocked_10__c', 'Month_Blocked_11__c', 'Month_Blocked_12__c' + }); qbCount.addColumn('count()'); if (this.employeeNameSearch != null && this.employeeNameSearch != '') { @@ -75,7 +81,7 @@ public class FTETrackerController extends FTEController { qb.addAnd('Year__c', '' + this.fteYear, QueryBuilder.QB_DECIMAL_TYPE); qbCount.addAnd('Year__c', '' + this.fteYear, QueryBuilder.QB_DECIMAL_TYPE); - qb.orderBy('Total_Hours__c', QueryBuilder.QB_DESC); + qb.orderBy('Total__c', QueryBuilder.QB_DESC); qb.setLimit(this.employeePagination.pageSize); qb.setOffset(this.employeePagination.getOffsetValue()); @@ -87,27 +93,38 @@ public class FTETrackerController extends FTEController { for (FTE_Work_Card__c workCard : workCards) { Integer empNetworkDays = FTETrackerHelper.getNetworkDays(workCard.Employee__r.Hire_Date__c, this.fteYear); FTEEmployeeTime empTime = new FTEEmployeeTime(workCard.Employee__r.Name, workCard.Employee__c); - empTime.daysArray[0] = workCard.Month_1__c; - empTime.daysArray[1] = workCard.Month_2__c; - empTime.daysArray[2] = workCard.Month_3__c; - empTime.daysArray[3] = workCard.Month_4__c; - empTime.daysArray[4] = workCard.Month_5__c; - empTime.daysArray[5] = workCard.Month_6__c; - empTime.daysArray[6] = workCard.Month_7__c; - empTime.daysArray[7] = workCard.Month_8__c; - empTime.daysArray[8] = workCard.Month_9__c; - empTime.daysArray[9] = workCard.Month_10__c; - empTime.daysArray[10] = workCard.Month_11__c; - empTime.daysArray[11] = workCard.Month_12__c; + SObject sObj = (SObject) workCard; + + Boolean wasFuture = false; + Boolean wasBlocked = false; + + for (Integer month = 1; month <= 12; month++) { + Boolean futureValue = (Boolean) sObj.get(FTETrackerHelper.getFieldTemplateFutureName(month)); + Boolean blockedValue = (Boolean) sObj.get(FTETrackerHelper.getFieldTemplateBlockedName(month)); + Decimal realValue = (Decimal) sObj.get(FTETrackerHelper.getFieldName(month)); + empTime.daysArray[month - 1] = realValue; + if (empTime.daysArray[month - 1] > 21) { + empTime.cssStyle[month - 1] = empTime.cssStyle[month - 1] + ' overbilled'; + } + if (futureValue == true) { + wasFuture = true; + empTime.cssStyle[month - 1] = empTime.cssStyle[month - 1] + ' future'; + } else if (blockedValue == true) { + wasBlocked = true; + empTime.cssStyle[month - 1] = empTime.cssStyle[month - 1] + ' blocked'; + } + } + empTime.daysArray[12] = workCard.Total__c; if (workCard.Total__c > empNetworkDays) { empTime.cssStyle[12] = 'fteCell overbilled'; } - for (Integer i = 0; i < 12; i++) { - if (empTime.daysArray[i] > 21) { - empTime.cssStyle[i] = 'fteCell overbilled'; - } + if (wasFuture == true) { + empTime.cssStyle[12] = empTime.cssStyle[12] + ' future'; + } else if (wasBlocked == true) { + empTime.cssStyle[12] = empTime.cssStyle[12] + ' blocked'; } + this.employees.add(empTime); } diff --git a/src/classes/FTETrackerHelper.cls b/src/classes/FTETrackerHelper.cls index e5108dd4..a20bf94b 100644 --- a/src/classes/FTETrackerHelper.cls +++ b/src/classes/FTETrackerHelper.cls @@ -11,14 +11,13 @@ public class FTETrackerHelper { public static JobWrapper loadWorkCardJobStatus() { // We want block FTE Tracker until all Work Cards will be generated from time cards JobWrapper workCardJobStatus = new JobWrapper(false); - ApexClass[] batchClassArray = [SELECT Id, Name FROM ApexClass WHERE Name='FTEGenerateEmployeesWorkCardBatch' OR Name = 'FTEHoursUploadBatch']; + ApexClass[] batchClassArray = [SELECT Id, Name FROM ApexClass WHERE Name = 'FTEGenerateEmployeesWorkCardBatch' OR Name = 'FTETimeAllocator' OR Name = 'FTEUpdateTagsBatch']; AsyncApexJob[] batchClassJobList = [SELECT Id, ApexClassID, JobItemsProcessed, TotalJobItems, CreatedDate FROM AsyncApexJob WHERE ApexClassID IN: batchClassArray AND Status IN ('Holding', 'Queued', 'Preparing', 'Processing') ORDER BY createdDate DESC LIMIT 1]; if(batchClassJobList != null && batchClassJobList.size() > 0) { workCardJobStatus.isRunning = true; - workCardJobStatus.jobName = batchClassArray[0].Id == batchClassJobList[0].ApexClassID ? (batchClassArray[0].Name == 'FTEHoursUploadBatch' ? 'CSV Upload Batch' : 'FTE Working Card Batch' ) - : (batchClassArray[1].Name == 'FTEHoursUploadBatch' ? 'CSV Upload Batch' : 'FTE Working Card Batch' ); + workCardJobStatus.jobName = 'FTE Time Allocator'; workCardJobStatus.jobItemsProcessed = batchClassJobList[0].JobItemsProcessed; workCardJobStatus.totalJobItems = batchClassJobList[0].TotalJobItems; } else { @@ -61,4 +60,59 @@ public class FTETrackerHelper { tData.add(new SelectOption(String.valueOf(currentYear - 1), String.valueOf(currentYear - 1))); return tData; } + + public static String getFieldName(Integer month) { + return 'Month_' + month + '__c'; + } + + public static String getFieldTemplateFutureName(Integer month) { + return 'Month_Future_' + month + '__c'; + } + + public static String getFieldTemplateBlockedName(Integer month) { + return 'Month_Blocked_' + month + '__c'; + } + + public static String getFieldUpdatedName(Integer month) { + return 'Month_Updated_' + month + '__c'; + } + + public static void markTemplates(Set contracts) { + List templates = [SELECT Id, Month_Updated_1__c, Month_Updated_2__c, Month_Updated_3__c, Month_Updated_4__c, Month_Updated_5__c, + Month_Updated_6__c, Month_Updated_7__c, Month_Updated_8__c, Month_Updated_9__c, Month_Updated_10__c, Month_Updated_11__c, Month_Updated_12__c + FROM FTE_Data_Record__c WHERE Contract__c IN: contracts]; + if (templates.size() > 0) { + for (FTE_Data_Record__c template : templates) { + template.Month_Updated_1__c = true; + template.Month_Updated_2__c = true; + template.Month_Updated_3__c = true; + template.Month_Updated_4__c = true; + template.Month_Updated_5__c = true; + template.Month_Updated_6__c = true; + template.Month_Updated_7__c = true; + template.Month_Updated_8__c = true; + template.Month_Updated_9__c = true; + template.Month_Updated_10__c = true; + template.Month_Updated_11__c = true; + template.Month_Updated_12__c = true; + } + update templates; + } + } + + public static Boolean isContractFTE(Id contractId) { + List contracts = [SELECT Id, FTE_Tracker__c FROM DContract__c WHERE Id =: contractId]; + if (contracts.size() > 0) { + return contracts.get(0).FTE_Tracker__c == 'Yes'; + } + return false; + } + + public static Boolean isWorkingDay(Integer year, Integer month, Integer day) { + String dayName = DateTime.newInstance(year, month, day).format('E'); + if (dayName != 'Sat' && dayName != 'Sun') { + return true; + } + return false; + } } \ No newline at end of file diff --git a/src/classes/FTETrackerTest.cls b/src/classes/FTETrackerTest.cls index 161f9367..d7a0dd05 100644 --- a/src/classes/FTETrackerTest.cls +++ b/src/classes/FTETrackerTest.cls @@ -133,170 +133,120 @@ public class FTETrackerTest { insert timeCards; } + + + + /** + * Work Cards tests + */ @isTest public static void shouldGenerateWorkCards() { + FTE_Work_Card__c oldWorkCard = new FTE_Work_Card__c(Employee__c = [SELECT Id FROM SFDC_Employee__c LIMIT 1].Id, Year__c = Date.today().year(), Month_1__c = 0, + Month_2__c = 0, Month_3__c = 0, Month_4__c = 0, Month_5__c = 0, Month_6__c = 0, + Month_7__c = 0, Month_8__c = 0, Month_9__c = 0, Month_10__c = 0, Month_11__c = 0, + Month_12__c = 0, Total__c = 0, Total_Hours__c = 0); // should be deleted + insert oldWorkCard; + Test.startTest(); - Database.executeBatch(new FTEGenerateEmployeesWorkCardBatch(Date.today().year())); + Database.executeBatch(new FTEGenerateEmployeesWorkCardBatch()); Test.stopTest(); List employees = [SELECT Id, Name FROM SFDC_Employee__c WHERE Employee_Status__c = 'Active' ORDER BY Name]; System.assertEquals(3, employees.size()); List workCards = [SELECT Id, Employee__c, Employee__r.Name, Month_1__c, Month_2__c, Month_3__c, Month_4__c, Month_5__c, Month_6__c, Month_7__c, Month_8__c, Month_9__c, Month_10__c, Month_11__c, Month_12__c, - Total__c, Total_Hours__c, Year__c FROM FTE_Work_Card__c ORDER BY Employee__r.Name]; + Total__c, Total_Hours__c, Year__c FROM FTE_Work_Card__c WHERE Year__c =: Date.today().year() ORDER BY Employee__r.Name]; System.assertEquals(3, workCards.size()); System.assertEquals(92.1, workCards.get(0).Total_Hours__c); System.assertEquals(11.5, workCards.get(0).Total__c); + System.assertEquals(0, [SELECT Id FROM FTE_Work_Card__c WHERE Id =: oldWorkCard.Id].size()); } @isTest - public static void triggerShouldGenerateWorkCards() { + public static void shouldGenerateWorkCardsByWorkCardId() { + Id employeeId = [SELECT Id FROM SFDC_Employee__c WHERE Employee_Status__c = 'Active' ORDER BY Name LIMIT 1].Id; + FTE_Work_Card__c oldWorkCard = new FTE_Work_Card__c(Employee__c = employeeId, Year__c = Date.today().year(), Month_1__c = 0, + Month_2__c = 0, Month_3__c = 0, Month_4__c = 0, Month_5__c = 0, Month_6__c = 0, + Month_7__c = 0, Month_8__c = 0, Month_9__c = 0, Month_10__c = 0, Month_11__c = 0, + Month_12__c = 0, Total__c = 0, Total_Hours__c = 0); // should be deleted + insert oldWorkCard; + Test.startTest(); - DContract__c contractToUpdate1 = [SELECT Id, FTE_Tracker__c FROM DContract__c WHERE Name = 'Unassigned Contract 1' LIMIT 1]; - contractToUpdate1.FTE_Tracker__c = 'Yes'; - DContract__c contractToUpdate2 = [SELECT Id, FTE_Tracker__c FROM DContract__c WHERE Name = 'FTE Contract 1' LIMIT 1]; - contractToUpdate2.FTE_Tracker__c = 'No'; - List contractsToUpdate = new List(); - contractsToUpdate.add(contractToUpdate1); - contractsToUpdate.add(contractToUpdate2); - update contractsToUpdate; + Database.executeBatch(new FTEGenerateEmployeesWorkCardBatch(new Set { oldWorkCard.Id })); Test.stopTest(); - List employees = [SELECT Id, Name FROM SFDC_Employee__c WHERE Employee_Status__c = 'Active' ORDER BY Name]; System.assertEquals(3, employees.size()); List workCards = [SELECT Id, Employee__c, Employee__r.Name, Month_1__c, Month_2__c, Month_3__c, Month_4__c, Month_5__c, Month_6__c, Month_7__c, Month_8__c, Month_9__c, Month_10__c, Month_11__c, Month_12__c, Total__c, Total_Hours__c, Year__c FROM FTE_Work_Card__c WHERE Year__c =: Date.today().year() ORDER BY Employee__r.Name]; - System.assertEquals(3, workCards.size()); - System.assertEquals(85.8, workCards.get(0).Total_Hours__c); // -25,6 + 19,3 - System.assertEquals(10.75, workCards.get(0).Total__c); - } - - @isTest - public static void shouldMoveHoursToUnassigned() { - Date currentDate = Date.today(); - addTimeCard([SELECT Id FROM DContract__c WHERE Name = 'FTE Contract 1' LIMIT 1].Id, - [SELECT Id FROM SFDC_Employee__c WHERE Name = 'Other Employee'].Id, - Date.newInstance(currentDate.year(), currentDate.month(), 5), 3.3); - SFDC_Employee__c employee = [SELECT Id FROM SFDC_Employee__c WHERE Name = 'FTE Employee']; - PageReference pageRef = Page.FTE_Employee_View; - pageRef.getParameters().put('employeeId', String.valueOf(employee.Id)); - Test.setCurrentPage(pageRef); - - Test.startTest(); - FTEEmployeeController controller = new FTEEmployeeController(); - controller.initFteEmployeeView(); - controller.employeeMonth = currentDate.month() - 1; - controller.contractId = [SELECT Id FROM DContract__c WHERE Name = 'FTE Contract 2' LIMIT 1].Id; - controller.loadEmployeeUnassMonth(); - controller.fteDays = '3'; - controller.moveTimeToUnassigned(); - controller.initFteEmployeeView(); - Test.stopTest(); - - System.assertEquals(70.9, controller.fteTimeManager.unassigned.hoursArray[currentDate.month() - 1]); - System.assertEquals(8.75, controller.fteTimeManager.unassigned.daysArray[currentDate.month() - 1]); - System.assertEquals(68.1, controller.totalAssignedDays.hoursArray[currentDate.month() - 1]); - System.assertEquals(8.5, controller.totalAssignedDays.daysArray[currentDate.month() - 1]); + System.assertEquals(1, workCards.size()); + System.assertEquals(92.1, workCards.get(0).Total_Hours__c); + System.assertEquals(11.5, workCards.get(0).Total__c); } @isTest - public static void shouldAddErrorWhenMovingHoursToUnassigned() { - Date currentDate = Date.today(); - SFDC_Employee__c employee = [SELECT Id FROM SFDC_Employee__c WHERE Name = 'FTE Employee']; - PageReference pageRef = Page.FTE_Employee_View; - pageRef.getParameters().put('employeeId', String.valueOf(employee.Id)); - Test.setCurrentPage(pageRef); - - Test.startTest(); - FTEEmployeeController controller = new FTEEmployeeController(); - controller.initFteEmployeeView(); - controller.employeeMonth = currentDate.month() - 1; - controller.contractId = [SELECT Id FROM DContract__c WHERE Name = 'FTE Contract 2' LIMIT 1].Id; - controller.loadEmployeeUnassMonth(); - controller.fteDays = '13'; - controller.moveTimeToUnassigned(); - - List msgs = ApexPages.getMessages(); - Boolean b = false; - System.debug('msgs : ' + msgs); - for(Apexpages.Message msg:msgs){ - if (msg.getDetail().contains('Too much hours to assign / hours cannot be negative')) b = true; - } - Test.stopTest(); - - System.assert(b); - } + public static void shouldGenerateWorkCardsWIthTemplateData() { + List employees = [SELECT Id, Name FROM SFDC_Employee__c WHERE Employee_Status__c = 'Active' ORDER BY Name]; - @isTest - public static void shouldMoveHoursFromUnassigned() { - Date currentDate = Date.today(); - SFDC_Employee__c employee = [SELECT Id FROM SFDC_Employee__c WHERE Name = 'FTE Employee']; - PageReference pageRef = Page.FTE_Employee_View; - pageRef.getParameters().put('employeeId', String.valueOf(employee.Id)); - Test.setCurrentPage(pageRef); + DContract__c fteContract1 = [SELECT Id, FTE_Tracker__c FROM DContract__c WHERE Name = 'FTE Contract 1' LIMIT 1]; + Dcontract__c fteContract2 = addContract('FTE Contract Fake 1', 'Yes'); + Dcontract__c fteContract3 = addContract('FTE Contract Fake 2', 'No'); + + FTE_Data_Record__c record1 = new FTE_Data_Record__c(Employee__c = employees.get(0).Id, Contract__c = fteContract2.Id, Year__c = Date.today().year()); + FTE_Data_Record__c record2 = new FTE_Data_Record__c(Employee__c = employees.get(0).Id, Contract__c = fteContract3.Id, Year__c = Date.today().year()); // this should be skipped + FTE_Data_Record__c record3 = new FTE_Data_Record__c(Employee__c = employees.get(0).Id, Contract__c = fteContract1.Id, Year__c = Date.today().year()); + SObject sObj1 = (SObject) record1; + SObject sObj2 = (SObject) record2; + SObject sObj3 = (SObject) record3; + sObj1.put(FTETrackerHelper.getFieldName(Date.today().month()), 4); + sObj2.put(FTETrackerHelper.getFieldName(Date.today().month()), 8); + sObj3.put(FTETrackerHelper.getFieldName(Date.today().month()), 6); + insert sObj1; + insert sObj2; + insert sObj3; Test.startTest(); - FTEEmployeeController controller = new FTEEmployeeController(); - controller.initFteEmployeeView(); - controller.employeeMonth = currentDate.month() - 1; - controller.loadEmployeeMonth(); - controller.fteDays = '2.5'; - controller.selectedFteContract = [SELECT Id FROM DContract__c WHERE Name = 'FTE Contract 1' LIMIT 1].Id; - controller.moveTimeFromUnassigned(); - controller.initFteEmployeeView(); + Database.executeBatch(new FTEGenerateEmployeesWorkCardBatch()); Test.stopTest(); - System.assertEquals(26.9, controller.fteTimeManager.unassigned.hoursArray[currentDate.month() - 1]); - System.assertEquals(3.25, controller.fteTimeManager.unassigned.daysArray[currentDate.month() - 1]); - System.assertEquals(112.1, controller.totalAssignedDays.hoursArray[currentDate.month() - 1]); - System.assertEquals(14, controller.totalAssignedDays.daysArray[currentDate.month() - 1]); + System.assertEquals(3, employees.size()); + List workCards = [SELECT Id, Employee__c, Employee__r.Name, Month_1__c, Month_2__c, Month_3__c, + Month_4__c, Month_5__c, Month_6__c, Month_7__c, Month_8__c, Month_9__c, Month_10__c, Month_11__c, Month_12__c, + Total__c, Total_Hours__c, Year__c FROM FTE_Work_Card__c WHERE Year__c =: Date.today().year() ORDER BY Employee__r.Name]; + System.assertEquals(3, workCards.size()); + System.assertEquals(146.5, workCards.get(0).Total_Hours__c); + System.assertEquals(18.25, workCards.get(0).Total__c); } @isTest - public static void shouldAddErrorWhenMovingHoursFromUnassigned() { - Date currentDate = Date.today(); - SFDC_Employee__c employee = [SELECT Id FROM SFDC_Employee__c WHERE Name = 'FTE Employee']; - PageReference pageRef = Page.FTE_Employee_View; - pageRef.getParameters().put('employeeId', String.valueOf(employee.Id)); - Test.setCurrentPage(pageRef); - + public static void triggerShouldGenerateWorkCards() { Test.startTest(); - FTEEmployeeController controller = new FTEEmployeeController(); - controller.initFteEmployeeView(); - controller.employeeMonth = currentDate.month() - 1; - controller.loadEmployeeMonth(); - controller.fteDays = '6'; - controller.moveTimeFromUnassigned(); - - List msgs = ApexPages.getMessages(); - Boolean b = false; - for(Apexpages.Message msg:msgs){ - if (msg.getDetail().contains('Too much hours to assign / hours cannot be negative')) b = true; - } + DContract__c contractToUpdate1 = [SELECT Id, FTE_Tracker__c FROM DContract__c WHERE Name = 'Unassigned Contract 1' LIMIT 1]; + contractToUpdate1.FTE_Tracker__c = 'Yes'; + DContract__c contractToUpdate2 = [SELECT Id, FTE_Tracker__c FROM DContract__c WHERE Name = 'FTE Contract 1' LIMIT 1]; + contractToUpdate2.FTE_Tracker__c = 'No'; + List contractsToUpdate = new List(); + contractsToUpdate.add(contractToUpdate1); + contractsToUpdate.add(contractToUpdate2); + update contractsToUpdate; + Database.executeBatch(new FTEGenerateEmployeesWorkCardBatch()); Test.stopTest(); - System.assert(b); + List employees = [SELECT Id, Name FROM SFDC_Employee__c WHERE Employee_Status__c = 'Active' ORDER BY Name]; + System.assertEquals(3, employees.size()); + List workCards = [SELECT Id, Employee__c, Employee__r.Name, Month_1__c, Month_2__c, Month_3__c, + Month_4__c, Month_5__c, Month_6__c, Month_7__c, Month_8__c, Month_9__c, Month_10__c, Month_11__c, Month_12__c, + Total__c, Total_Hours__c, Year__c FROM FTE_Work_Card__c WHERE Year__c =: Date.today().year() ORDER BY Employee__r.Name]; + System.assertEquals(3, workCards.size()); + System.assertEquals(85.8, workCards.get(0).Total_Hours__c); // -25,6 + 19,3 + System.assertEquals(10.75, workCards.get(0).Total__c); } - @isTest - public static void shouldGenerateEmployeeHours() { - Date currentDate = Date.today(); - SFDC_Employee__c employee = [SELECT Id FROM SFDC_Employee__c WHERE Name = 'FTE Employee']; - PageReference pageRef = Page.FTE_Employee_View; - pageRef.getParameters().put('employeeId', String.valueOf(employee.Id)); - Test.setCurrentPage(pageRef); - Test.startTest(); - FTEEmployeeController controller = new FTEEmployeeController(); - controller.initFteEmployeeView(); - Test.stopTest(); - System.assertEquals(46.9, controller.fteTimeManager.unassigned.hoursArray[currentDate.month() - 1]); - System.assertEquals(5.75, controller.fteTimeManager.unassigned.daysArray[currentDate.month() - 1]); - System.assertEquals(92.1, controller.totalAssignedDays.hoursArray[currentDate.month() - 1]); - System.assertEquals(11.5, controller.totalAssignedDays.daysArray[currentDate.month() - 1]); - } + /** + * Tags tests + */ @isTest public static void triggerShouldAddRemovedTags() { Id cont1 = [SELECT Id FROM DContract__c WHERE Name = 'FTE Contract 1' LIMIT 1].Id; @@ -392,164 +342,424 @@ public class FTETrackerTest { System.assertEquals(contUn2, tc2b.FTE_Contract__c); } -/** + + + + /** + * FTE Triggers tests + */ + @isTest + public static void testContractTrigger() { + SFDC_Employee__c empl1 = addEmployee('FTE test1'); + SFDC_Employee__c empl2 = addEmployee('FTE test2'); + DContract__c dContract1 = addContract('Trigger contract 1', 'No'); + DContract__c dContract2 = addContract('Trigger contract 2', 'No'); + DContract__c dContract3 = addContract('Trigger contract 2', 'No'); + + insert new List { + new FTE_Data_Record__c(Contract__c = dContract1.Id, Employee__c = empl1.Id), + new FTE_Data_Record__c(Contract__c = dContract2.Id, Employee__c = empl1.Id), + new FTE_Data_Record__c(Contract__c = dContract3.Id, Employee__c = empl1.Id), + new FTE_Data_Record__c(Contract__c = dContract1.Id, Employee__c = empl2.Id) + }; + + Test.startTest(); + dContract1.FTE_Tracker__c = 'Yes'; + dContract2.FTE_Tracker__c = 'Yes'; + dContract3.FTE_Tracker__c = 'Yes'; + update new List { dContract1, dContract2, dContract3 }; + Test.stopTest(); + + List tamplates = [SELECT Id, Month_Updated_1__c, Month_Updated_2__c, Month_Updated_3__c, Month_Updated_4__c, Month_Updated_5__c, + Month_Updated_6__c, Month_Updated_7__c, Month_Updated_8__c, Month_Updated_9__c, Month_Updated_10__c, Month_Updated_11__c, Month_Updated_12__c + FROM FTE_Data_Record__c]; + System.assertEquals(4, tamplates.size()); + assertTemplateUpdated(tamplates.get(0)); + assertTemplateUpdated(tamplates.get(1)); + assertTemplateUpdated(tamplates.get(2)); + assertTemplateUpdated(tamplates.get(3)); + } + @isTest - public static void batchShouldFillNegativeFTETags1() { - SFDC_Employee__c employee = [SELECT Id FROM SFDC_Employee__c WHERE Name = 'Other Employee']; - SFDC_Employee__c employee2 = [SELECT Id FROM SFDC_Employee__c WHERE Name = 'Yyy Employee']; - Id cont1 = [SELECT Id FROM DContract__c WHERE Name = 'FTE Contract 1' LIMIT 1].Id; - Id cont3 = [SELECT Id FROM DContract__c WHERE Name = 'FTE Contract 3' LIMIT 1].Id; - Id cont4 = [SELECT Id FROM DContract__c WHERE Name = 'FTE Contract 4' LIMIT 1].Id; - Id contUn1 = [SELECT Id FROM DContract__c WHERE Name = 'Unassigned Contract 1' LIMIT 1].Id; - Id contUn2 = [SELECT Id FROM DContract__c WHERE Name = 'Unassigned Contract 2' LIMIT 1].Id; + public static void testTimeLoggedTrigger() { + SFDC_Employee__c empl1 = addEmployee('FTE test1'); + SFDC_Employee__c empl2 = addEmployee('FTE test2'); + DContract__c dContract1 = addContract('Trigger contract 1', 'Yes'); + DContract__c dContract2 = addContract('Trigger contract 2', 'Yes'); + DContract__c dContract3 = addContract('Trigger contract 3', 'Yes'); + Integer currentYear = Date.today().year(); + insert new List { + new FTE_Data_Record__c(Year__c = currentYear, Contract__c = dContract1.Id, Employee__c = empl1.Id), + new FTE_Data_Record__c(Year__c = currentYear, Contract__c = dContract2.Id, Employee__c = empl1.Id), + new FTE_Data_Record__c(Year__c = currentYear, Contract__c = dContract3.Id, Employee__c = empl1.Id), + new FTE_Data_Record__c(Year__c = currentYear, Contract__c = dContract1.Id, Employee__c = empl2.Id) + }; Test.startTest(); + List testTimeCards = new List { + new Time_Card__c(Total__c = 2, Date__c = Date.newInstance(currentYear, 3, 1), Employee__c = empl1.Id, Client__c = dContract1.Id), + new Time_Card__c(Total__c = 2, Date__c = Date.newInstance(currentYear, 3, 21), Employee__c = empl1.Id, Client__c = dContract1.Id), + new Time_Card__c(Total__c = 2, Date__c = Date.newInstance(currentYear, 3, 3), Employee__c = empl1.Id, Client__c = dContract1.Id), + new Time_Card__c(Total__c = 2, Date__c = Date.newInstance(currentYear, 1, 1), Employee__c = empl1.Id, Client__c = dContract1.Id), + new Time_Card__c(Total__c = 2, Date__c = Date.newInstance(currentYear, 1, 5), Employee__c = empl1.Id, Client__c = dContract1.Id), + new Time_Card__c(Total__c = 2, Date__c = Date.newInstance(currentYear, 1, 6), Employee__c = empl1.Id, Client__c = dContract1.Id), + new Time_Card__c(Total__c = 2, Date__c = Date.newInstance(currentYear, 9, 7), Employee__c = empl1.Id, Client__c = dContract1.Id), + new Time_Card__c(Total__c = 2, Date__c = Date.newInstance(currentYear, 12, 5), Employee__c = empl1.Id, Client__c = dContract2.Id), + new Time_Card__c(Total__c = 2, Date__c = Date.newInstance(currentYear, 12, 6), Employee__c = empl1.Id, Client__c = dContract2.Id), + new Time_Card__c(Total__c = 2, Date__c = Date.newInstance(currentYear, 12, 7), Employee__c = empl1.Id, Client__c = dContract2.Id), + new Time_Card__c(Total__c = 2, Date__c = Date.newInstance(currentYear, 11, 5), Employee__c = empl1.Id, Client__c = dContract2.Id), + new Time_Card__c(Total__c = 2, Date__c = Date.newInstance(currentYear, 10, 6), Employee__c = empl1.Id, Client__c = dContract2.Id), + new Time_Card__c(Total__c = 2, Date__c = Date.newInstance(currentYear, 9, 7), Employee__c = empl1.Id, Client__c = dContract2.Id), + new Time_Card__c(Total__c = 2, Date__c = Date.newInstance(currentYear, 10, 5), Employee__c = empl1.Id, Client__c = dContract3.Id), + new Time_Card__c(Total__c = 2, Date__c = Date.newInstance(currentYear, 7, 6), Employee__c = empl1.Id, Client__c = dContract3.Id), + new Time_Card__c(Total__c = 2, Date__c = Date.newInstance(currentYear, 6, 7), Employee__c = empl1.Id, Client__c = dContract3.Id), + new Time_Card__c(Total__c = 2, Date__c = Date.newInstance(currentYear, 6, 5), Employee__c = empl1.Id, Client__c = dContract3.Id), + new Time_Card__c(Total__c = 2, Date__c = Date.newInstance(currentYear, 6, 6), Employee__c = empl1.Id, Client__c = dContract3.Id), + new Time_Card__c(Total__c = 2, Date__c = Date.newInstance(currentYear, 5, 7), Employee__c = empl1.Id, Client__c = dContract3.Id), + new Time_Card__c(Total__c = 2, Date__c = Date.newInstance(currentYear, 3, 5), Employee__c = empl1.Id, Client__c = dContract3.Id), + new Time_Card__c(Total__c = 2, Date__c = Date.newInstance(currentYear, 4, 6), Employee__c = empl1.Id, Client__c = dContract3.Id), + new Time_Card__c(Total__c = 2, Date__c = Date.newInstance(currentYear, 8, 7), Employee__c = empl2.Id, Client__c = dContract1.Id), + new Time_Card__c(Total__c = 2, Date__c = Date.newInstance(currentYear, 8, 5), Employee__c = empl2.Id, Client__c = dContract1.Id), + new Time_Card__c(Total__c = 2, Date__c = Date.newInstance(currentYear, 8, 6), Employee__c = empl2.Id, Client__c = dContract1.Id), + new Time_Card__c(Total__c = 2, Date__c = Date.newInstance(currentYear, 8, 7), Employee__c = empl2.Id, Client__c = dContract1.Id) + }; + insert testTimeCards; + Test.stopTest(); - List toDel = new List(); - List toUpd = new List(); + List tamplates = [SELECT Id, Month_Updated_1__c, Month_Updated_2__c, Month_Updated_3__c, Month_Updated_4__c, Month_Updated_5__c, + Month_Updated_6__c, Month_Updated_7__c, Month_Updated_8__c, Month_Updated_9__c, Month_Updated_10__c, Month_Updated_11__c, Month_Updated_12__c + FROM FTE_Data_Record__c]; + System.assertEquals(4, tamplates.size()); + + FTE_Data_Record__c testTemplate = [SELECT Id, Month_Updated_1__c, Month_Updated_2__c, Month_Updated_3__c, Month_Updated_4__c, Month_Updated_5__c, + Month_Updated_6__c, Month_Updated_7__c, Month_Updated_8__c, Month_Updated_9__c, Month_Updated_10__c, Month_Updated_11__c, Month_Updated_12__c + FROM FTE_Data_Record__c WHERE Contract__c =: dContract1.Id AND Employee__c =: empl1.Id LIMIT 1]; + assertTemplateUpdated(testTemplate, true, false, true, false, false, false, false, false, true, false, false, false); + + testTemplate = [SELECT Id, Month_Updated_1__c, Month_Updated_2__c, Month_Updated_3__c, Month_Updated_4__c, Month_Updated_5__c, + Month_Updated_6__c, Month_Updated_7__c, Month_Updated_8__c, Month_Updated_9__c, Month_Updated_10__c, Month_Updated_11__c, Month_Updated_12__c + FROM FTE_Data_Record__c WHERE Contract__c =: dContract2.Id AND Employee__c =: empl1.Id LIMIT 1]; + assertTemplateUpdated(testTemplate, false, false, false, false, false, false, false, false, true, true, true, true); + + testTemplate = [SELECT Id, Month_Updated_1__c, Month_Updated_2__c, Month_Updated_3__c, Month_Updated_4__c, Month_Updated_5__c, + Month_Updated_6__c, Month_Updated_7__c, Month_Updated_8__c, Month_Updated_9__c, Month_Updated_10__c, Month_Updated_11__c, Month_Updated_12__c + FROM FTE_Data_Record__c WHERE Contract__c =: dContract3.Id AND Employee__c =: empl1.Id LIMIT 1]; + assertTemplateUpdated(testTemplate, false, false, true, true, true, true, true, false, false, true, false, false); + + testTemplate = [SELECT Id, Month_Updated_1__c, Month_Updated_2__c, Month_Updated_3__c, Month_Updated_4__c, Month_Updated_5__c, + Month_Updated_6__c, Month_Updated_7__c, Month_Updated_8__c, Month_Updated_9__c, Month_Updated_10__c, Month_Updated_11__c, Month_Updated_12__c + FROM FTE_Data_Record__c WHERE Contract__c =: dContract1.Id AND Employee__c =: empl2.Id LIMIT 1]; + assertTemplateUpdated(testTemplate, false, false, false, false, false, false, false, true, false, false, false, false); + } - Time_Card__c tc1 = [SELECT Id, Total__c FROM Time_Card__c WHERE Employee__c =: employee.Id AND Client__c =: cont4 LIMIT 1]; - Time_Card__c tc2 = [SELECT Id, Total__c FROM Time_Card__c WHERE Employee__c =: employee.Id AND Client__c =: cont1 AND Total__c = 6 LIMIT 1]; - toDel.add(tc1); - toDel.add(tc2); + @isTest + public static void testTriggerWhenUnassignedWasLogged() { + SFDC_Employee__c empl1 = addEmployee('FTE test1'); + DContract__c dContract1 = addContract('Trigger contract 1', 'Yes'); + DContract__c dContract2 = addContract('Trigger contract 2', 'Yes'); + DContract__c dContract3 = addContract('Trigger contract 3', 'No'); + DContract__c dContract4 = addContract('Trigger contract 4', 'No'); + Integer currentYear = Date.today().year(); + insert new List { + new FTE_Data_Record__c(Year__c = currentYear, Contract__c = dContract1.Id, Employee__c = empl1.Id) + }; - Time_Card__c tc3 = [SELECT Id, Total__c FROM Time_Card__c WHERE Employee__c =: employee.Id AND Client__c =: cont1 AND Total__c = 4.3 LIMIT 1]; - Time_Card__c tc4 = [SELECT Id, Total__c FROM Time_Card__c WHERE Employee__c =: employee.Id AND Client__c =: cont1 AND Total__c = 3.3 LIMIT 1]; - tc3.Total__c = 1.3; - tc4.Total__c = 2.3; + Test.startTest(); + List testTimeCards = new List { + new Time_Card__c(Total__c = 2, Date__c = Date.newInstance(currentYear, 3, 1), Employee__c = empl1.Id, Client__c = dContract3.Id), + new Time_Card__c(Total__c = 2, Date__c = Date.newInstance(currentYear, 3, 21), Employee__c = empl1.Id, Client__c = dContract3.Id), + new Time_Card__c(Total__c = 2, Date__c = Date.newInstance(currentYear, 3, 3), Employee__c = empl1.Id, Client__c = dContract3.Id), + new Time_Card__c(Total__c = 2, Date__c = Date.newInstance(currentYear, 1, 1), Employee__c = empl1.Id, Client__c = dContract3.Id) + }; + insert testTimeCards; + Test.stopTest(); - toDel.addAll([SELECT Id, Total__c FROM Time_Card__c WHERE Employee__c =: employee2.Id AND Client__c =: cont4]); - Time_Card__c tc5 = [SELECT Id, Total__c FROM Time_Card__c WHERE Employee__c =: employee2.Id AND Client__c =: cont1 AND Total__c = 4 LIMIT 1]; - Time_Card__c tc6 = [SELECT Id, Total__c FROM Time_Card__c WHERE Employee__c =: employee2.Id AND Client__c =: cont1 AND Total__c = 5 LIMIT 1]; - tc5.Total__c = 1; - tc6.Total__c = 1; + List tamplates = [SELECT Id, Month_Updated_1__c, Month_Updated_2__c, Month_Updated_3__c, Month_Updated_4__c, Month_Updated_5__c, + Month_Updated_6__c, Month_Updated_7__c, Month_Updated_8__c, Month_Updated_9__c, Month_Updated_10__c, Month_Updated_11__c, Month_Updated_12__c + FROM FTE_Data_Record__c]; + System.assertEquals(1, tamplates.size()); + assertTemplateUpdated(tamplates.get(0), true, false, true, false, false, false, false, false, false, false, false, false); + } - toUpd.add(tc3); - toUpd.add(tc4); - toUpd.add(tc5); - toUpd.add(tc6); - delete toDel; - update toUpd; - Database.executeBatch(new FTEUpdateTagsBatch(true)); - Test.stopTest(); + /** + * FTE Data Manager tests + */ + @isTest + public static void testTimeManagerEmpl1() { + SFDC_Employee__c employee = [SELECT Id FROM SFDC_Employee__c WHERE Name = 'FTE Employee' LIMIT 1]; + Id contract1Id = [SELECT Id FROM DContract__c WHERE Name = 'FTE Contract 1' LIMIT 1].Id; + Id contract2Id = [SELECT Id FROM DContract__c WHERE Name = 'FTE Contract 2' LIMIT 1].Id; + Id contract3Id = [SELECT Id FROM DContract__c WHERE Name = 'FTE Contract 3' LIMIT 1].Id; + Id contract4Id = [SELECT Id FROM DContract__c WHERE Name = 'FTE Contract 4' LIMIT 1].Id; - List results = sumHours([SELECT Id, Total__c, FTE_Hours__c, FTE_Contract__c FROM Time_Card__c WHERE Client__c =: cont1 AND Employee__r.Name = 'Other Employee']); - System.assertEquals(18.6, results.get(0)); - System.assertEquals(5, results.get(1)); + Test.startTest(); + FTEDataManager timeManager = new FTEDataManager(Date.today().year(), employee.Id); + timeManager.loadEmployeeTime(); + Integer month = Date.today().month(); - Time_Card__c testTimeCard = [SELECT Id, FTE_Only__c, FTE_Contract__c, Total__c, FTE_Hours__c, Client__c FROM Time_Card__c WHERE - Client__r.Name = 'FTE Contract 4' AND Employee__r.Name = 'Other Employee' LIMIT 1]; - System.assertEquals(cont4, testTimeCard.Client__c); - System.assertEquals(true, testTimeCard.FTE_only__c); - System.assertEquals(0, testTimeCard.FTE_Hours__c); - System.assertEquals(null, testTimeCard.FTE_Contract__c); + System.assertEquals(25.6, timeManager.assignedMap.get(contract1Id).hoursArray[month - 1]); + System.assertEquals(25.6, timeManager.assignedMap.get(contract2Id).hoursArray[month - 1]); + System.assertEquals(10.3, timeManager.assignedMap.get(contract3Id).hoursArray[month - 1]); + System.assertEquals(30.6, timeManager.assignedMap.get(contract4Id).hoursArray[month - 1]); + System.assertEquals(19.3 + 27.6, timeManager.unassigned.hoursArray[month - 1]); + + System.assertEquals(0, [SELECT Id FROM Time_Card__c WHERE Employee__c =: employee.Id AND FTE_Contract__r.FTE_Tracker__c = 'No'].size()); + System.assertEquals(0, [SELECT Id FROM Time_Card__c WHERE Employee__c =: employee.Id AND FTE_Contract__r.FTE_Tracker__c = 'Yes'].size()); + + timeManager.removeTime(contract1Id, 24, Date.today().month()); + System.assertEquals(1, [SELECT Id FROM Time_Card__c WHERE Client__c =: contract1Id AND Employee__c =: employee.Id AND FTE_Contract__r.FTE_Tracker__c = 'No'].size()); - results = sumHours([SELECT Id, Total__c, FTE_Hours__c, FTE_Contract__c FROM Time_Card__c WHERE Client__c =: cont1 AND Employee__r.Name = 'Yyy Employee']); - System.assertEquals(12.1, results.get(0)); - System.assertEquals(8, results.get(1)); + timeManager.addTime(contract3Id, 8, Date.today().month()); + System.assertEquals(1, [SELECT Id FROM Time_Card__c WHERE FTE_Contract__c =: contract3Id AND Employee__c =: employee.Id AND FTE_Contract__r.FTE_Tracker__c = 'Yes'].size()); + timeManager.addTime(contract3Id, 2, Date.today().month()); + System.assertEquals(1, [SELECT Id FROM Time_Card__c WHERE FTE_Contract__c =: contract3Id AND Employee__c =: employee.Id AND FTE_Contract__r.FTE_Tracker__c = 'Yes'].size()); - testTimeCard = [SELECT Id, FTE_Only__c, FTE_Contract__c, Total__c, FTE_Hours__c, Client__c FROM Time_Card__c WHERE - Client__r.Name = 'FTE Contract 4' AND Employee__r.Name = 'Yyy Employee' LIMIT 1]; - System.assertEquals(cont4, testTimeCard.Client__c); - System.assertEquals(true, testTimeCard.FTE_only__c); - System.assertEquals(0, testTimeCard.Total__c); - System.assertEquals(2, testTimeCard.FTE_Hours__c); - System.assertEquals(cont3, testTimeCard.FTE_Contract__c); + timeManager.removeTime(contract3Id, 5, Date.today().month()); + System.assertEquals(1, [SELECT Id FROM Time_Card__c WHERE FTE_Contract__c =: contract3Id AND Employee__c =: employee.Id AND FTE_Contract__r.FTE_Tracker__c = 'Yes'].size()); + System.assertEquals(1, [SELECT Id FROM Time_Card__c WHERE FTE_Contract__c =: contract3Id AND Employee__c =: employee.Id AND FTE_Contract__r.FTE_Tracker__c = 'Yes' + AND FTE_Hours__c = 5].size()); + Test.stopTest(); } @isTest - public static void batchShouldFillNegativeFTETags2() { - SFDC_Employee__c employee = [SELECT Id FROM SFDC_Employee__c WHERE Name = 'Other Employee']; - SFDC_Employee__c employee2 = [SELECT Id FROM SFDC_Employee__c WHERE Name = 'Yyy Employee']; - Id cont1 = [SELECT Id FROM DContract__c WHERE Name = 'FTE Contract 1' LIMIT 1].Id; - Id cont2 = [SELECT Id FROM DContract__c WHERE Name = 'FTE Contract 2' LIMIT 1].Id; - Id cont3 = [SELECT Id FROM DContract__c WHERE Name = 'FTE Contract 3' LIMIT 1].Id; - Id cont4 = [SELECT Id FROM DContract__c WHERE Name = 'FTE Contract 4' LIMIT 1].Id; - Id contUn1 = [SELECT Id FROM DContract__c WHERE Name = 'Unassigned Contract 1' LIMIT 1].Id; - Id contUn2 = [SELECT Id FROM DContract__c WHERE Name = 'Unassigned Contract 2' LIMIT 1].Id; + public static void testTimeManagerEmpl2() { + SFDC_Employee__c employee = [SELECT Id FROM SFDC_Employee__c WHERE Name = 'Other Employee' LIMIT 1]; + Id contract1Id = [SELECT Id FROM DContract__c WHERE Name = 'FTE Contract 1' LIMIT 1].Id; + Id contract2Id = [SELECT Id FROM DContract__c WHERE Name = 'FTE Contract 2' LIMIT 1].Id; + Id contract3Id = [SELECT Id FROM DContract__c WHERE Name = 'FTE Contract 3' LIMIT 1].Id; + Id contract4Id = [SELECT Id FROM DContract__c WHERE Name = 'FTE Contract 4' LIMIT 1].Id; Test.startTest(); + FTEDataManager timeManager = new FTEDataManager(Date.today().year(), employee.Id); + timeManager.loadEmployeeTime(); + Integer month = Date.today().month(); + + System.assertEquals(28.6 - 5, timeManager.assignedMap.get(contract1Id).hoursArray[month - 1]); + System.assertEquals(null, timeManager.assignedMap.get(contract2Id)); + System.assertEquals(null, timeManager.assignedMap.get(contract3Id)); + System.assertEquals(5 - 2, timeManager.assignedMap.get(contract4Id).hoursArray[month - 1]); + System.assertEquals(19.3 + 5 + 2, timeManager.unassigned.hoursArray[month - 1]); + Test.stopTest(); + } + + @isTest + public static void testTimeManagerEmpl3() { + SFDC_Employee__c employee = [SELECT Id FROM SFDC_Employee__c WHERE Name = 'Yyy Employee' LIMIT 1]; + Id contract1Id = [SELECT Id FROM DContract__c WHERE Name = 'FTE Contract 1' LIMIT 1].Id; + Id contract2Id = [SELECT Id FROM DContract__c WHERE Name = 'FTE Contract 2' LIMIT 1].Id; + Id contract3Id = [SELECT Id FROM DContract__c WHERE Name = 'FTE Contract 3' LIMIT 1].Id; + Id contract4Id = [SELECT Id FROM DContract__c WHERE Name = 'FTE Contract 4' LIMIT 1].Id; - List toDel = new List(); - List toUpd = new List(); - - Time_Card__c tc1 = [SELECT Id, Total__c FROM Time_Card__c WHERE Employee__c =: employee.Id AND Client__c =: cont4 LIMIT 1]; - Time_Card__c tc2 = [SELECT Id, Total__c FROM Time_Card__c WHERE Employee__c =: employee.Id AND Client__c =: cont1 AND Total__c = 6 LIMIT 1]; - Time_Card__c tc3 = [SELECT Id, Total__c FROM Time_Card__c WHERE Employee__c =: employee.Id AND Client__c =: cont1 AND Total__c = 8 LIMIT 1]; - Time_Card__c tc4 = [SELECT Id, Total__c FROM Time_Card__c WHERE Employee__c =: employee.Id AND Client__c =: cont1 AND Total__c = 7 LIMIT 1]; - Time_Card__c tc5 = [SELECT Id, Total__c FROM Time_Card__c WHERE Employee__c =: employee.Id AND Client__c =: cont1 AND Total__c = 4.3 LIMIT 1]; - toDel.add(tc1); - toDel.add(tc2); - toDel.add(tc3); - toDel.add(tc4); - toDel.add(tc5); - - Time_Card__c tc6 = [SELECT Id, Total__c FROM Time_Card__c WHERE Employee__c =: employee2.Id AND Client__c =: cont1 AND Total__c = 4 LIMIT 1]; - Time_Card__c tc7 = [SELECT Id, Total__c FROM Time_Card__c WHERE Employee__c =: employee2.Id AND Client__c =: cont1 AND Total__c = 5 LIMIT 1]; - Time_Card__c tc8 = [SELECT Id, Total__c FROM Time_Card__c WHERE Employee__c =: employee2.Id AND Client__c =: cont1 AND Total__c = 4.6 LIMIT 1]; - Time_Card__c tc9 = [SELECT Id, Total__c FROM Time_Card__c WHERE Employee__c =: employee2.Id AND Client__c =: cont1 AND Total__c = 2.5 LIMIT 1]; - tc6.Total__c = 0.5; - tc7.Total__c = 0.5; - tc8.Total__c = 0.5; - toDel.add(tc9); - toUpd.add(tc6); - toUpd.add(tc7); - toUpd.add(tc8); - - Time_Card__c tc10 = [SELECT Id, Total__c FROM Time_Card__c WHERE Employee__c =: employee2.Id AND Client__c =: cont2 AND Total__c = 7 LIMIT 1]; - Time_Card__c tc11 = [SELECT Id, Total__c FROM Time_Card__c WHERE Employee__c =: employee2.Id AND Client__c =: cont2 AND Total__c = 9 LIMIT 1]; - tc10.Total__c = 1; - tc11.Total__c = 7; - toUpd.add(tc10); - toUpd.add(tc11); - - Time_Card__c tc12 = [SELECT Id, Total__c FROM Time_Card__c WHERE Employee__c =: employee2.Id AND Client__c =: cont3 AND Total__c = 4 LIMIT 1]; - toDel.add(tc12); - - toDel.addAll([SELECT Id FROM Time_Card__c WHERE Client__c =: contUn2 AND Employee__r.Name = 'Yyy Employee']); - - delete toDel; - update toUpd; - Database.executeBatch(new FTEUpdateTagsBatch(true)); + Test.startTest(); + FTEDataManager timeManager = new FTEDataManager(Date.today().year(), employee.Id); + timeManager.loadEmployeeTime(); + Integer month = Date.today().month(); + System.assertEquals(19.1 - 8, timeManager.assignedMap.get(contract1Id).hoursArray[month - 1]); + System.assertEquals(16 - 7 + 5, timeManager.assignedMap.get(contract2Id).hoursArray[month - 1]); + System.assertEquals(7 - 5 + 3 + 16, timeManager.assignedMap.get(contract3Id).hoursArray[month - 1]); + System.assertEquals(8 - 3 + 2, timeManager.assignedMap.get(contract4Id).hoursArray[month - 1]); + System.assertEquals(3 + 22 + 8 + 7 - 16 - 2, timeManager.unassigned.hoursArray[month - 1]); Test.stopTest(); + } + - List results = sumHours([SELECT Id, Total__c, FTE_Hours__c, FTE_Contract__c FROM Time_Card__c WHERE Client__c =: cont1 AND Employee__r.Name = 'Other Employee']); - System.assertEquals(3.3, results.get(0)); - System.assertEquals(3.3, results.get(1)); - Time_Card__c tc2b = [SELECT Id, FTE_Only__c, FTE_Contract__c, Total__c, FTE_Hours__c, Client__c FROM Time_Card__c WHERE - Client__r.Name = 'FTE Contract 4' AND Employee__r.Name = 'Other Employee' LIMIT 1]; - System.assertEquals(cont4, tc2b.Client__c); - System.assertEquals(true, tc2b.FTE_only__c); - System.assertEquals(0, tc2b.FTE_Hours__c); - System.assertEquals(null, tc2b.FTE_Contract__c); - results = sumHours([SELECT Id, Total__c, FTE_Hours__c, FTE_Contract__c FROM Time_Card__c WHERE Client__c =: cont1 AND Employee__r.Name = 'Yyy Employee']); - System.assertEquals(4.5, results.get(0)); - System.assertEquals(4.5, results.get(1)); + /** + * Time Allocation tests + */ + @isTest + public static void shouldSetDefaultValuesForTemplate() { + Id employeeId = [SELECT Id FROM SFDC_Employee__c LIMIT 1].Id; + Id contractId = [SELECT Id FROM DContract__c WHERE FTE_Tracker__c = 'Yes' LIMIT 1].Id; + + FTE_Data_Record__c newRecord = new FTE_Data_Record__c(Employee__c = employeeId, Year__c = Date.today().year(), Contract__c = contractId); + insert newRecord; + + FTE_Data_Record__c dataFromDB = [SELECT Id, Month_1__c, Month_2__c, Month_3__c, Month_4__c, Month_5__c, Month_6__c, + Month_7__c, Month_8__c, Month_9__c, Month_10__c, Month_11__c, Month_12__c, + Month_Updated_1__c, Month_Updated_2__c, Month_Updated_3__c, Month_Updated_4__c, + Month_Updated_5__c, Month_Updated_6__c, Month_Updated_7__c, Month_Updated_8__c, + Month_Updated_9__c, Month_Updated_10__c, Month_Updated_11__c, Month_Updated_12__c + FROM FTE_Data_Record__c WHERE Id =: newRecord.Id LIMIT 1]; + SObject sObj = (SObject) dataFromDB; + for (Integer month = 1; month <= 12; month++) { + System.assertEquals(-1, sObj.get(FTETrackerHelper.getFieldName(month))); + System.assertEquals(false, sObj.get(FTETrackerHelper.getFieldUpdatedName(month))); + } + } - results = sumHours([SELECT Id, Total__c, FTE_Hours__c, FTE_Contract__c FROM Time_Card__c WHERE Client__c =: cont2 AND Employee__r.Name = 'Yyy Employee']); - System.assertEquals(8, results.get(0)); - System.assertEquals(7, results.get(1)); + @isTest + public static void shouldGenerateCorrectEmployeeMonths() { + FTETimeAllocator allocator = new FTETimeAllocator(true); + List employeeMonths = allocator.start(null); + System.assertEquals(0, employeeMonths.size()); + + List emplList = [SELECT Id FROM SFDC_Employee__c]; + Id employee1Id = emplList.get(0).Id; + Id employee2Id = emplList.get(1).Id; + List contractList = [SELECT Id FROM DContract__c WHERE FTE_Tracker__c = 'Yes']; + Id contract1Id = contractList.get(0).Id; + Id contract2Id = contractList.get(1).Id; + + FTE_Data_Record__c newRecord = new FTE_Data_Record__c(Employee__c = employee1Id, Year__c = Date.today().year(), Contract__c = contract1Id); + insert newRecord; + employeeMonths = allocator.start(null); + System.assertEquals(0, employeeMonths.size()); + + update new FTE_Data_Record__c(Id = newRecord.Id, Month_Updated_2__c = true); + employeeMonths = allocator.start(null); + System.assertEquals(1, employeeMonths.size()); + + update new FTE_Data_Record__c(Id = newRecord.Id, Month_Updated_3__c = true); + employeeMonths = allocator.start(null); + System.assertEquals(2, employeeMonths.size()); + + update new FTE_Data_Record__c(Id = newRecord.Id, Month_Updated_5__c = true); + employeeMonths = allocator.start(null); + System.assertEquals(3, employeeMonths.size()); + + insert new FTE_Data_Record__c(Employee__c = employee1Id, Year__c = Date.today().year(), Contract__c = contract2Id, Month_Updated_5__c = true); + employeeMonths = allocator.start(null); + System.assertEquals(3, employeeMonths.size()); + + insert new FTE_Data_Record__c(Employee__c = employee1Id, Year__c = Date.today().year() - 1, Contract__c = contract2Id, Month_Updated_5__c = true); + employeeMonths = allocator.start(null); + System.assertEquals(4, employeeMonths.size()); + + insert new FTE_Data_Record__c(Employee__c = employee2Id, Year__c = Date.today().year(), Contract__c = contract2Id, Month_Updated_4__c = true, Month_Updated_5__c = true); + employeeMonths = allocator.start(null); + System.assertEquals(6, employeeMonths.size()); + } - results = sumHours([SELECT Id, Total__c, FTE_Hours__c, FTE_Contract__c FROM Time_Card__c WHERE Client__c =: cont3 AND Employee__r.Name = 'Yyy Employee']); - System.assertEquals(3, results.get(0)); - System.assertEquals(5, results.get(1)); + @isTest + public static void shouldAllocateTimeForEmployees() { + Id employee1 = [SELECT Id FROM SFDC_Employee__c WHERE Name = 'FTE Employee' LIMIT 1].Id; + Id employee2 = [SELECT Id FROM SFDC_Employee__c WHERE Name = 'Other Employee' LIMIT 1].Id; + Id employee3 = [SELECT Id FROM SFDC_Employee__c WHERE Name = 'Yyy Employee' LIMIT 1].Id; + Id contract1 = [SELECT Id FROM DContract__c WHERE Name = 'FTE Contract 1' LIMIT 1].Id; + Id contract2 = [SELECT Id FROM DContract__c WHERE Name = 'FTE Contract 2' LIMIT 1].Id; + Id contract3 = [SELECT Id FROM DContract__c WHERE Name = 'FTE Contract 3' LIMIT 1].Id; + Id contract4 = [SELECT Id FROM DContract__c WHERE Name = 'FTE Contract 4' LIMIT 1].Id; + Integer month = Date.today().month(); + Integer year = Date.today().year(); + + // Templates + insert new List { + buildContractTEmplate(employee1, contract1, 3.0), + buildContractTEmplate(employee1, contract2, 3.0), + buildContractTEmplate(employee1, contract3, 1.0), + buildContractTEmplate(employee2, contract1, 4.0), + buildContractTEmplate(employee2, contract2, 3.0), + buildContractTEmplate(employee2, contract3, 0.0), + buildContractTEmplate(employee2, contract4, 0.0), + buildContractTEmplate(employee3, contract1, 2.5), + buildContractTEmplate(employee3, contract2, 1.0), + buildContractTEmplate(employee3, contract4, 1.0) + }; - results = sumHours([SELECT Id, Total__c, FTE_Hours__c, FTE_Contract__c FROM Time_Card__c WHERE Client__c =: contUn2 AND Employee__r.Name = 'Yyy Employee']); - System.assertEquals(0, results.get(0)); - System.assertEquals(0, results.get(1)); + Test.startTest(); + Database.executeBatch(new FTETimeAllocator(false)); + Test.stopTest(); - results = sumHours([SELECT Id, Total__c, FTE_Hours__c, FTE_Contract__c FROM Time_Card__c WHERE Client__c =: cont4 AND Employee__r.Name = 'Yyy Employee']); - System.assertEquals(8, results.get(0)); - System.assertEquals(3, results.get(1)); + // Assert Employee 1 : FTE Employee + FTEDataManager timeManager = new FTEDataManager(year, month, employee1, true); + timeManager.loadEmployeeTime(); + timeManager.loadTemplateTime(); + + timeManager.assignedMap.get(contract1).calculateDaysAndTotal(); + System.assertEquals(24, timeManager.assignedMap.get(contract1).hoursArray[month - 1]); + System.assertEquals(3, timeManager.assignedMap.get(contract1).templateArray[month - 1]); + System.assertEquals(3, timeManager.assignedMap.get(contract1).daysArray[month - 1]); + + timeManager.assignedMap.get(contract2).calculateDaysAndTotal(); + System.assertEquals(24, timeManager.assignedMap.get(contract2).hoursArray[month - 1]); + System.assertEquals(3, timeManager.assignedMap.get(contract2).templateArray[month - 1]); + System.assertEquals(3, timeManager.assignedMap.get(contract2).daysArray[month - 1]); + + timeManager.assignedMap.get(contract3).calculateDaysAndTotal(); + System.assertEquals(8, timeManager.assignedMap.get(contract3).hoursArray[month - 1]); + System.assertEquals(1, timeManager.assignedMap.get(contract3).templateArray[month - 1]); + System.assertEquals(1, timeManager.assignedMap.get(contract3).daysArray[month - 1]); + + timeManager.assignedMap.get(contract4).calculateDaysAndTotal(); + System.assertEquals(30.6, timeManager.assignedMap.get(contract4).hoursArray[month - 1]); + System.assertEquals(-1, timeManager.assignedMap.get(contract4).templateArray[month - 1]); + System.assertEquals(3.75, timeManager.assignedMap.get(contract4).daysArray[month - 1]); + + timeManager.unassigned.calculateDaysAndTotal(); + System.assertEquals(52.4, timeManager.unassigned.hoursArray[month - 1]); + System.assertEquals(6.5, timeManager.unassigned.daysArray[month - 1]); + + // Assert Employee 2 : Other Employe + timeManager = new FTEDataManager(year, month, employee2, true); + timeManager.loadEmployeeTime(); + timeManager.loadTemplateTime(); + + timeManager.assignedMap.get(contract1).calculateDaysAndTotal(); + System.assertEquals(32, timeManager.assignedMap.get(contract1).hoursArray[month - 1]); + System.assertEquals(4, timeManager.assignedMap.get(contract1).templateArray[month - 1]); + System.assertEquals(4, timeManager.assignedMap.get(contract1).daysArray[month - 1]); + + timeManager.assignedMap.get(contract2).calculateDaysAndTotal(); + System.assertEquals(20.9, timeManager.assignedMap.get(contract2).hoursArray[month - 1]); + System.assertEquals(3, timeManager.assignedMap.get(contract2).templateArray[month - 1]); + System.assertEquals(3, timeManager.assignedMap.get(contract2).daysArray[month - 1]); + + timeManager.assignedMap.get(contract3).calculateDaysAndTotal(); + System.assertEquals(0, timeManager.assignedMap.get(contract3).hoursArray[month - 1]); + System.assertEquals(0, timeManager.assignedMap.get(contract3).templateArray[month - 1]); + System.assertEquals(0, timeManager.assignedMap.get(contract3).daysArray[month - 1]); + + timeManager.assignedMap.get(contract4).calculateDaysAndTotal(); + System.assertEquals(0, timeManager.assignedMap.get(contract4).hoursArray[month - 1]); + System.assertEquals(0, timeManager.assignedMap.get(contract4).templateArray[month - 1]); + System.assertEquals(0, timeManager.assignedMap.get(contract4).daysArray[month - 1]); + + timeManager.unassigned.calculateDaysAndTotal(); + System.assertEquals(0, timeManager.unassigned.hoursArray[month - 1]); + System.assertEquals(0, timeManager.unassigned.daysArray[month - 1]); + + // Assert Employee 3 : Yyy Employe + timeManager = new FTEDataManager(year, month, employee3, true); + timeManager.loadEmployeeTime(); + timeManager.loadTemplateTime(); + + timeManager.assignedMap.get(contract1).calculateDaysAndTotal(); + System.assertEquals(20, timeManager.assignedMap.get(contract1).hoursArray[month - 1]); + System.assertEquals(2.5, timeManager.assignedMap.get(contract1).templateArray[month - 1]); + System.assertEquals(2.5, timeManager.assignedMap.get(contract1).daysArray[month - 1]); + + timeManager.assignedMap.get(contract2).calculateDaysAndTotal(); + System.assertEquals(8, timeManager.assignedMap.get(contract2).hoursArray[month - 1]); + System.assertEquals(1, timeManager.assignedMap.get(contract2).templateArray[month - 1]); + System.assertEquals(1, timeManager.assignedMap.get(contract2).daysArray[month - 1]); + + timeManager.assignedMap.get(contract3).calculateDaysAndTotal(); + System.assertEquals(21, timeManager.assignedMap.get(contract3).hoursArray[month - 1]); + System.assertEquals(-1, timeManager.assignedMap.get(contract3).templateArray[month - 1]); + System.assertEquals(2.75, timeManager.assignedMap.get(contract3).daysArray[month - 1]); + + timeManager.assignedMap.get(contract4).calculateDaysAndTotal(); + System.assertEquals(8, timeManager.assignedMap.get(contract4).hoursArray[month - 1]); + System.assertEquals(1, timeManager.assignedMap.get(contract4).templateArray[month - 1]); + System.assertEquals(1, timeManager.assignedMap.get(contract4).daysArray[month - 1]); + + timeManager.unassigned.calculateDaysAndTotal(); + System.assertEquals(18.1, timeManager.unassigned.hoursArray[month - 1]); + System.assertEquals(2.25, timeManager.unassigned.daysArray[month - 1]); } -*/ + + + + /** + * Time Cards generation tests + */ @isTest public static void shouldGenerateCSVDataWithoutTags() { SFDC_Employee__c employee = [SELECT Id FROM SFDC_Employee__c WHERE Name = 'FTE Employee']; @@ -982,41 +1192,248 @@ public class FTETrackerTest { } @isTest - public static void csvParseControllerTest() { - Integer fteYear = Date.today().year(); - integer fteMonth = Date.today().month(); - String correctData = ',,,,,,\n\r ' - + 'FTE Contract 1 ,01/' + fteYear + ', 02/' + fteYear + ', 03/' + fteYear + ', 04/' + fteYear + ', 05/' + fteYear + ', 06/' + fteYear + ', 07/' + fteYear - + ', 08/' + fteYear + ', 09/' + fteYear + ', 10/' + fteYear + ', 11/' + fteYear + ', 12/' + fteYear + '\n\r' - + 'Other Employee,1,2,3,4,5,6,7,8,9,10,11,12\n\r' - + 'FTE Employee,3,4,5,6,7,8,9,10,11,12,13,14\n\r' - + 'Yyy Employee,12,11,10,9,8,7,6,5,4,3,2,1\n\r'; - FTECsvUploadController csvUpload = new FTECsvUploadController(); + public static void shouldGenerateTimeCardsForFuture() { + SFDC_Employee__c employee = [SELECT Id FROM SFDC_Employee__c WHERE Name = 'FTE Employee' LIMIT 1]; + DContract__c dContract1 = [SELECT Id FROM DContract__c WHERE Name = 'FTE Contract 1' LIMIT 1]; + DContract__c dContract2 = [SELECT Id FROM DContract__c WHERE Name = 'FTE Contract 2' LIMIT 1]; - Test.startTest(); - csvUpload.fileContent = Blob.valueOf(correctData); - csvUpload.fileName = 'Test file name'; + Integer fteMonth = 4; // we want know when is weekend + Integer fteYear = 2019; - csvUpload.parseCsvFile(); + insert buildContractTemplate(employee.Id, dContract1.Id, 8, fteYear, fteMonth); + insert buildContractTemplate(employee.Id, dContract2.Id, 6, fteYear, fteMonth); + + Test.startTest(); + FTETimeCardGenerator generator = new FTETimeCardGenerator(fteYear, fteMonth, employee.Id); + List employeeMonthProjects = generator.generateMonthTimeCards(); Test.stopTest(); - System.assertEquals(3, csvUpload.records.size()); - assertFTEDataRecord(csvUpload.records.values(), 'Other Employee', fteYear, 'FTE Contract 1', 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); - assertFTEDataRecord(csvUpload.records.values(), 'FTE Employee', fteYear, 'FTE Contract 1', 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14); - assertFTEDataRecord(csvUpload.records.values(), 'Yyy Employee', fteYear, 'FTE Contract 1', 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1); + System.assertEquals(3, employeeMonthProjects.size()); // FTE 1, FTE 2, Overhead + + // Overhead + System.assertEquals(0, employeeMonthProjects.get(2).hours[0]); + System.assertEquals(0, employeeMonthProjects.get(2).hours[1]); + System.assertEquals(0, employeeMonthProjects.get(2).totalHours); + System.assertEquals(-1, employeeMonthProjects.get(2).templateExpectedTime); + + // FTE Contract 1 + System.assertEquals(8, employeeMonthProjects.get(0).hours[0]); + System.assertEquals(8, employeeMonthProjects.get(0).hours[1]); + System.assertEquals(8, employeeMonthProjects.get(0).hours[2]); + System.assertEquals(8, employeeMonthProjects.get(0).hours[3]); + System.assertEquals(8, employeeMonthProjects.get(0).hours[4]); + System.assertEquals(8, employeeMonthProjects.get(0).hours[7]); + System.assertEquals(8, employeeMonthProjects.get(0).hours[8]); + System.assertEquals(8, employeeMonthProjects.get(0).hours[9]); + System.assertEquals(8 * 8, employeeMonthProjects.get(0).totalHours); + System.assertEquals(8, employeeMonthProjects.get(0).templateExpectedTime); + + // FTE Contract 2 + System.assertEquals(8, employeeMonthProjects.get(1).hours[10]); + System.assertEquals(8, employeeMonthProjects.get(1).hours[11]); + System.assertEquals(8, employeeMonthProjects.get(1).hours[14]); + System.assertEquals(8, employeeMonthProjects.get(1).hours[15]); + System.assertEquals(8, employeeMonthProjects.get(1).hours[16]); + System.assertEquals(8, employeeMonthProjects.get(1).hours[17]); + System.assertEquals(8 * 6, employeeMonthProjects.get(1).totalHours); + System.assertEquals(6, employeeMonthProjects.get(1).templateExpectedTime); } @isTest - public static void shouldUploadAndMoveCSVDataSample1() { + public static void shouldGenerateTimeCardsForFutureOverEigthHours() { + SFDC_Employee__c employee = [SELECT Id FROM SFDC_Employee__c WHERE Name = 'FTE Employee' LIMIT 1]; + DContract__c dContract1 = [SELECT Id FROM DContract__c WHERE Name = 'FTE Contract 1' LIMIT 1]; + DContract__c dContract2 = [SELECT Id FROM DContract__c WHERE Name = 'FTE Contract 2' LIMIT 1]; + + Integer fteMonth = 4; // we want know when is weekend, 22 possible working days + Integer fteYear = 2019; + + insert buildContractTemplate(employee.Id, dContract1.Id, 20, fteYear, fteMonth); + insert buildContractTemplate(employee.Id, dContract2.Id, 2.5, fteYear, fteMonth); + + Test.startTest(); + FTETimeCardGenerator generator = new FTETimeCardGenerator(fteYear, fteMonth, employee.Id); + List employeeMonthProjects = generator.generateMonthTimeCards(); + Test.stopTest(); + + System.assertEquals(3, employeeMonthProjects.size()); // FTE 1, FTE 2, Overhead + + // FTE Contract 1 + System.assertEquals(8, employeeMonthProjects.get(0).hours[0]); + System.assertEquals(8, employeeMonthProjects.get(0).hours[1]); + System.assertEquals(8, employeeMonthProjects.get(0).hours[2]); + System.assertEquals(8, employeeMonthProjects.get(0).hours[3]); + + // FTE Contract 2 + System.assertEquals(8, employeeMonthProjects.get(1).hours[29]); + System.assertEquals(1, employeeMonthProjects.get(1).hours[0]); + System.assertEquals(1, employeeMonthProjects.get(1).hours[1]); + System.assertEquals(1, employeeMonthProjects.get(1).hours[2]); + System.assertEquals(1, employeeMonthProjects.get(1).hours[3]); + System.assertEquals(0, employeeMonthProjects.get(1).hours[4]); + + System.assertEquals(2.5 * 8, employeeMonthProjects.get(1).totalHours); + System.assertEquals(2.5, employeeMonthProjects.get(1).templateExpectedTime); + } + + @isTest + public static void shouldFillFutureTimeForContractDays() { + SFDC_Employee__c employee = [SELECT Id FROM SFDC_Employee__c WHERE Name = 'FTE Employee' LIMIT 1]; + DContract__c dContract1 = [SELECT Id FROM DContract__c WHERE Name = 'FTE Contract 1' LIMIT 1]; + DContract__c dContract2 = [SELECT Id FROM DContract__c WHERE Name = 'FTE Contract 2' LIMIT 1]; + DContract__c unassigned1 = [SELECT Id FROM DContract__c WHERE Name = 'Unassigned Contract 1' LIMIT 1]; + + Integer fteMonth = 4; // we want know when is weekend, 22 possible working days + Integer fteYear = 2019; + + insert buildContractTemplate(employee.Id, dContract1.Id, 8, fteYear, fteMonth); + + List testTimeCards = new List { // Assigned time by days 1 -> 7, 2 -> 7, 5 -> 7, 9 -> 8, 10 -> 7, 11 -> 8, 22 -> 8, 23 -> 7, 24 -> 7, 25 -> 6, 26 -> 9 + new Time_Card__c(Total__c = 6, Date__c = Date.newInstance(fteYear, fteMonth, 1), Employee__c = employee.Id, Client__c = dContract1.Id), + new Time_Card__c(Total__c = 4, Date__c = Date.newInstance(fteYear, fteMonth, 2), Employee__c = employee.Id, Client__c = dContract1.Id), + new Time_Card__c(Total__c = 4, Date__c = Date.newInstance(fteYear, fteMonth, 5), Employee__c = employee.Id, Client__c = dContract1.Id), + new Time_Card__c(Total__c = 6, Date__c = Date.newInstance(fteYear, fteMonth, 9), Employee__c = employee.Id, Client__c = dContract1.Id), + new Time_Card__c(Total__c = 2, Date__c = Date.newInstance(fteYear, fteMonth, 10), Employee__c = employee.Id, Client__c = dContract1.Id), + new Time_Card__c(Total__c = 1, Date__c = Date.newInstance(fteYear, fteMonth, 11), Employee__c = employee.Id, Client__c = dContract1.Id), + new Time_Card__c(Total__c = 1, Date__c = Date.newInstance(fteYear, fteMonth, 22), Employee__c = employee.Id, Client__c = dContract1.Id), + new Time_Card__c(Total__c = 5, Date__c = Date.newInstance(fteYear, fteMonth, 23), Employee__c = employee.Id, Client__c = dContract1.Id), + new Time_Card__c(Total__c = 5, Date__c = Date.newInstance(fteYear, fteMonth, 24), Employee__c = employee.Id, Client__c = dContract1.Id), + new Time_Card__c(Total__c = 5, Date__c = Date.newInstance(fteYear, fteMonth, 25), Employee__c = employee.Id, Client__c = dContract1.Id), + new Time_Card__c(Total__c = 5, Date__c = Date.newInstance(fteYear, fteMonth, 26), Employee__c = employee.Id, Client__c = dContract1.Id), + + new Time_Card__c(Total__c = 1, Date__c = Date.newInstance(fteYear, fteMonth, 1), Employee__c = employee.Id, Client__c = dContract2.Id), + new Time_Card__c(Total__c = 3, Date__c = Date.newInstance(fteYear, fteMonth, 2), Employee__c = employee.Id, Client__c = dContract2.Id), + new Time_Card__c(Total__c = 5, Date__c = Date.newInstance(fteYear, fteMonth, 3), Employee__c = employee.Id, Client__c = dContract2.Id), + new Time_Card__c(Total__c = 5, Date__c = Date.newInstance(fteYear, fteMonth, 4), Employee__c = employee.Id, Client__c = dContract2.Id), + new Time_Card__c(Total__c = 3, Date__c = Date.newInstance(fteYear, fteMonth, 5), Employee__c = employee.Id, Client__c = dContract2.Id), + new Time_Card__c(Total__c = 2, Date__c = Date.newInstance(fteYear, fteMonth, 9), Employee__c = employee.Id, Client__c = dContract2.Id), + new Time_Card__c(Total__c = 5, Date__c = Date.newInstance(fteYear, fteMonth, 10), Employee__c = employee.Id, Client__c = dContract2.Id), + new Time_Card__c(Total__c = 7, Date__c = Date.newInstance(fteYear, fteMonth, 11), Employee__c = employee.Id, Client__c = dContract2.Id), + new Time_Card__c(Total__c = 7, Date__c = Date.newInstance(fteYear, fteMonth, 22), Employee__c = employee.Id, Client__c = dContract2.Id), + new Time_Card__c(Total__c = 2, Date__c = Date.newInstance(fteYear, fteMonth, 23), Employee__c = employee.Id, Client__c = dContract2.Id), + new Time_Card__c(Total__c = 2, Date__c = Date.newInstance(fteYear, fteMonth, 24), Employee__c = employee.Id, Client__c = dContract2.Id), + new Time_Card__c(Total__c = 1, Date__c = Date.newInstance(fteYear, fteMonth, 25), Employee__c = employee.Id, Client__c = dContract2.Id), + new Time_Card__c(Total__c = 4, Date__c = Date.newInstance(fteYear, fteMonth, 26), Employee__c = employee.Id, Client__c = dContract2.Id) + }; + insert testTimeCards; + + Test.startTest(); + FTETimeCardGenerator generator = new FTETimeCardGenerator(fteYear, fteMonth, employee.Id); + List employeeMonthProjects = generator.generateMonthTimeCards(); + Test.stopTest(); + + System.assertEquals(3, employeeMonthProjects.size()); // FTE 1, FTE 2, Overhead + + // FTE Contract 1 + System.assertEquals(7, employeeMonthProjects.get(0).hours[0]); + System.assertEquals(5, employeeMonthProjects.get(0).hours[1]); + System.assertEquals(3, employeeMonthProjects.get(0).hours[2]); // generated + System.assertEquals(3, employeeMonthProjects.get(0).hours[3]); // generated + System.assertEquals(5, employeeMonthProjects.get(0).hours[4]); + System.assertEquals(0, employeeMonthProjects.get(0).hours[5]); + System.assertEquals(0, employeeMonthProjects.get(0).hours[6]); + System.assertEquals(6, employeeMonthProjects.get(0).hours[7]); // generated + System.assertEquals(6, employeeMonthProjects.get(0).hours[8]); + System.assertEquals(3, employeeMonthProjects.get(0).hours[9]); + System.assertEquals(1, employeeMonthProjects.get(0).hours[10]); + System.assertEquals(0, employeeMonthProjects.get(0).hours[11]); + System.assertEquals(0, employeeMonthProjects.get(0).hours[12]); + System.assertEquals(0, employeeMonthProjects.get(0).hours[13]); + System.assertEquals(0, employeeMonthProjects.get(0).hours[14]); + System.assertEquals(0, employeeMonthProjects.get(0).hours[15]); + System.assertEquals(0, employeeMonthProjects.get(0).hours[16]); + System.assertEquals(0, employeeMonthProjects.get(0).hours[17]); + System.assertEquals(0, employeeMonthProjects.get(0).hours[18]); + System.assertEquals(0, employeeMonthProjects.get(0).hours[19]); + System.assertEquals(0, employeeMonthProjects.get(0).hours[20]); + System.assertEquals(1, employeeMonthProjects.get(0).hours[21]); + System.assertEquals(6, employeeMonthProjects.get(0).hours[22]); + System.assertEquals(6, employeeMonthProjects.get(0).hours[23]); + System.assertEquals(7, employeeMonthProjects.get(0).hours[24]); + System.assertEquals(5, employeeMonthProjects.get(0).hours[25]); + System.assertEquals(0, employeeMonthProjects.get(0).hours[26]); + System.assertEquals(0, employeeMonthProjects.get(0).hours[27]); + System.assertEquals(0, employeeMonthProjects.get(0).hours[28]); + System.assertEquals(0, employeeMonthProjects.get(0).hours[29]); + + System.assertEquals(8 * 8, employeeMonthProjects.get(0).totalHours); + System.assertEquals(8, employeeMonthProjects.get(0).templateExpectedTime); + + // FTE Contract 2 + System.assertEquals(1, employeeMonthProjects.get(1).hours[0]); + System.assertEquals(3, employeeMonthProjects.get(1).hours[1]); + System.assertEquals(5, employeeMonthProjects.get(1).hours[2]); // generated + System.assertEquals(5, employeeMonthProjects.get(1).hours[3]); // generated + System.assertEquals(3, employeeMonthProjects.get(1).hours[4]); + System.assertEquals(2, employeeMonthProjects.get(1).hours[8]); + System.assertEquals(5, employeeMonthProjects.get(1).hours[9]); + System.assertEquals(7, employeeMonthProjects.get(1).hours[10]); + System.assertEquals(7, employeeMonthProjects.get(1).hours[21]); + System.assertEquals(2, employeeMonthProjects.get(1).hours[22]); + System.assertEquals(2, employeeMonthProjects.get(1).hours[23]); + System.assertEquals(1, employeeMonthProjects.get(1).hours[24]); + System.assertEquals(4, employeeMonthProjects.get(1).hours[25]); + System.assertEquals(0, employeeMonthProjects.get(1).hours[26]); + + System.assertEquals(47, employeeMonthProjects.get(1).totalHours); + System.assertEquals(-1, employeeMonthProjects.get(1).templateExpectedTime); + } + + + + + /** + * CSV Parser Tests + */ + @isTest + public static void csvParseControllerTestFullFile() { Integer fteYear = Date.today().year(); integer fteMonth = Date.today().month(); String correctData = ',,,,,,\n\r ' + 'FTE Contract 1 ,01/' + fteYear + ', 02/' + fteYear + ', 03/' + fteYear + ', 04/' + fteYear + ', 05/' + fteYear + ', 06/' + fteYear + ', 07/' + fteYear + ', 08/' + fteYear + ', 09/' + fteYear + ', 10/' + fteYear + ', 11/' + fteYear + ', 12/' + fteYear + '\n\r' - + buildDataRow('Other Employee', fteMonth, 5.0) - + buildDataRow('FTE Employee', fteMonth, 7.0) - + buildDataRow('Yyy Employee', fteMonth, 4.0); + + 'Other Employee,1,2,3,4,5,6,7,8,9,10,11,12\n\r' + + 'FTE Employee,3,4,5,6,7,8,9,10,11,12,13,14\n\r' + + 'Yyy Employee,12,11,10,9,8,7,6,5,4,3,2,1\n\r'; + FTECsvUploadController csvUpload = new FTECsvUploadController(); + + Test.startTest(); + csvUpload.fileContent = Blob.valueOf(correctData); + csvUpload.fileName = 'Test file name'; + + csvUpload.parseCsvFile(); + Test.stopTest(); + + System.assertEquals(3, csvUpload.records.size()); + assertFTEDataRecord(csvUpload.records.values(), 'Other Employee', fteYear, 'FTE Contract 1', 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); + assertFTEDataRecord(csvUpload.records.values(), 'FTE Employee', fteYear, 'FTE Contract 1', 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14); + assertFTEDataRecord(csvUpload.records.values(), 'Yyy Employee', fteYear, 'FTE Contract 1', 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1); + List dbTemplates = [SELECT Id, Employee__r.Name, Contract__r.Name, Year__c, + Month_1__c, Month_2__c, Month_3__c, Month_4__c, Month_5__c, Month_6__c, + Month_7__c, Month_8__c, Month_9__c, Month_10__c, Month_11__c, Month_12__c, + Month_Updated_1__c, Month_Updated_2__c, Month_Updated_3__c, Month_Updated_4__c, Month_Updated_5__c, Month_Updated_6__c, + Month_Updated_7__c, Month_Updated_8__c, Month_Updated_9__c, Month_Updated_10__c, Month_Updated_11__c, Month_Updated_12__c + FROM FTE_Data_Record__c + WHERE Year__c =: fteYear ORDER BY Employee__r.Name]; + System.assertEquals(3, dbTemplates.size()); + + assertFTEDBDataRecord(dbTemplates.get(0), 'FTE Employee', fteYear, 'FTE Contract 1', 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14); + assertFTEDBDataRecord(dbTemplates.get(1), 'Other Employee', fteYear, 'FTE Contract 1', 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); + assertFTEDBDataRecord(dbTemplates.get(2), 'Yyy Employee', fteYear, 'FTE Contract 1', 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1); + } + + @isTest + public static void csvParseControllerTestPartialFile() { + Integer fteYear = Date.today().year(); + integer fteMonth = Date.today().month(); + String correctData = ',,,,,,\n\r ' + + 'FTE Contract 1 ,01/' + fteYear + ', 02/' + fteYear + ', 03/' + fteYear + ', 04/' + fteYear + '\n\r' + + 'Other Employee,1,,,0\n\r' + + 'Other Employee,5,2,,\n\r' + + 'Yyy Employee,4,4,4,0\n\r' + + 'FTE Employee,3,3,,1\n\r' + + 'Yyy Employee,,0,,2\n\r'; FTECsvUploadController csvUpload = new FTECsvUploadController(); Test.startTest(); @@ -1024,44 +1441,142 @@ public class FTETrackerTest { csvUpload.fileName = 'Test file name'; csvUpload.parseCsvFile(); + Test.stopTest(); + + System.assertEquals(3, csvUpload.records.size()); + assertFTEDataRecord(csvUpload.records.values(), 'Other Employee', fteYear, 'FTE Contract 1', 6, 2, -1, 0, -2, -2, -2, -2, -2, -2, -2, -2); + assertFTEDataRecord(csvUpload.records.values(), 'FTE Employee', fteYear, 'FTE Contract 1', 3, 3, -1, 1, -2, -2, -2, -2, -2, -2, -2, -2); + assertFTEDataRecord(csvUpload.records.values(), 'Yyy Employee', fteYear, 'FTE Contract 1', 4, 4, 4, 2, -2, -2, -2, -2, -2, -2, -2, -2); + + List dbTemplates = [SELECT Id, Employee__r.Name, Contract__r.Name, Year__c, + Month_1__c, Month_2__c, Month_3__c, Month_4__c, Month_5__c, Month_6__c, + Month_7__c, Month_8__c, Month_9__c, Month_10__c, Month_11__c, Month_12__c, + Month_Updated_1__c, Month_Updated_2__c, Month_Updated_3__c, Month_Updated_4__c, Month_Updated_5__c, Month_Updated_6__c, + Month_Updated_7__c, Month_Updated_8__c, Month_Updated_9__c, Month_Updated_10__c, Month_Updated_11__c, Month_Updated_12__c + FROM FTE_Data_Record__c + WHERE Year__c =: fteYear ORDER BY Employee__r.Name]; + System.assertEquals(3, dbTemplates.size()); + + assertFTEDBDataRecord(dbTemplates.get(0), 'FTE Employee', fteYear, 'FTE Contract 1', 3, 3, -1, 1, -1, -1, -1, -1, -1, -1, -1, -1); + assertFTEDBDataRecord(dbTemplates.get(1), 'Other Employee', fteYear, 'FTE Contract 1', 6, 2, -1, 0, -1, -1, -1, -1, -1, -1, -1, -1); + assertFTEDBDataRecord(dbTemplates.get(2), 'Yyy Employee', fteYear, 'FTE Contract 1', 4,4, 4, 2, -1, -1, -1, -1, -1, -1, -1, -1); + } + + @isTest + public static void csvParseControllerTestMergeTemplate() { + Integer fteYear = Date.today().year(); + integer fteMonth = Date.today().month(); + Id contractId = [SELECT Id FROM DContract__c WHERE Name = 'FTE Contract 1' LIMIT 1].Id; + Id employeeId1 = [SELECT Id FROM SFDC_Employee__c WHERE Name = 'FTE Employee' LIMIT 1].Id; + Id employeeId2 = [SELECT Id FROM SFDC_Employee__c WHERE Name = 'Yyy Employee' LIMIT 1].Id; + insert new List { + new FTE_Data_Record__c(Year__c = fteYear, Employee__c = employeeId1, Contract__c = contractId, Month_1__c = 3, Month_2__c = -1, Month_3__c = -1, Month_4__c = 0), + new FTE_Data_Record__c(Year__c = fteYear, Employee__c = employeeId2, Contract__c = contractId, Month_1__c = 3, Month_2__c = 4, Month_3__c = 0, Month_4__c = 2) + }; + + String correctData = ',,,,,,\n\r ' + + 'FTE Contract 1 ,01/' + fteYear + ', 02/' + fteYear + ', 03/' + fteYear + ', 04/' + fteYear + '\n\r' + + 'Other Employee,1,,,0\n\r' + + 'Other Employee,5,2,,\n\r' + + 'Yyy Employee,4,4,4,0\n\r' + + 'FTE Employee,3,3,,1\n\r' + + 'Yyy Employee,,0,,2\n\r'; + FTECsvUploadController csvUpload = new FTECsvUploadController(); + Test.startTest(); + csvUpload.fileContent = Blob.valueOf(correctData); + csvUpload.fileName = 'Test file name'; + csvUpload.parseCsvFile(); Test.stopTest(); - for (FTE_Data_Record_Status__c rec : [SELECT Status__c, Status_Message__c, Line_Number__c, Line_Number_Text__c FROM FTE_Data_Record_Status__c]) { - System.debug('Status Error Record : ' + rec); + System.assertEquals(3, csvUpload.records.size()); + assertFTEDataRecord(csvUpload.records.values(), 'Other Employee', fteYear, 'FTE Contract 1', 6, 2, -1, 0, -2, -2, -2, -2, -2, -2, -2, -2); + assertFTEDataRecord(csvUpload.records.values(), 'FTE Employee', fteYear, 'FTE Contract 1', 3, 3, -1, 1, -2, -2, -2, -2, -2, -2, -2, -2); + assertFTEDataRecord(csvUpload.records.values(), 'Yyy Employee', fteYear, 'FTE Contract 1', 4, 4, 4, 2, -2, -2, -2, -2, -2, -2, -2, -2); + + List dbTemplates = [SELECT Id, Employee__r.Name, Contract__r.Name, Year__c, + Month_1__c, Month_2__c, Month_3__c, Month_4__c, Month_5__c, Month_6__c, + Month_7__c, Month_8__c, Month_9__c, Month_10__c, Month_11__c, Month_12__c, + Month_Updated_1__c, Month_Updated_2__c, Month_Updated_3__c, Month_Updated_4__c, Month_Updated_5__c, Month_Updated_6__c, + Month_Updated_7__c, Month_Updated_8__c, Month_Updated_9__c, Month_Updated_10__c, Month_Updated_11__c, Month_Updated_12__c + FROM FTE_Data_Record__c + WHERE Year__c =: fteYear ORDER BY Employee__r.Name]; + for (FTE_Data_Record__c templ : dbTemplates) { + System.debug('Template data : ' + templ.Employee__r.Name + ' ' + templ.Year__c + ' ' + templ.Contract__r.Name); } - System.assertEquals(0, [SELECT count() FROM FTE_Data_Record_Status__c]); + System.assertEquals(3, dbTemplates.size()); + + assertFTEDBDataRecord(dbTemplates.get(0), 'FTE Employee', fteYear, 'FTE Contract 1', 3, 3, -1, 1, -1, -1, -1, -1, -1, -1, -1, -1); + assertFTEDBDataRecord(dbTemplates.get(1), 'Other Employee', fteYear, 'FTE Contract 1', 6, 2, -1, 0, -1, -1, -1, -1, -1, -1, -1, -1); + assertFTEDBDataRecord(dbTemplates.get(2), 'Yyy Employee', fteYear, 'FTE Contract 1', 4, 4, 4, 2, -1, -1, -1, -1, -1, -1, -1, -1); + assertTemplateUpdated(dbTemplates.get(0), false, true, false, true, false, false, false, false, false, false, false, false); + assertTemplateUpdated(dbTemplates.get(1), true, true, false, true, false, false, false, false, false, false, false, false); + assertTemplateUpdated(dbTemplates.get(2), true, false, true, false, false, false, false, false, false, false, false, false); + } + + @isTest + public static void shouldUploadAndMoveCSVDataSample1() { + Integer fteYear = Date.today().year(); + integer fteMonth = Date.today().month(); + String correctData = ',,,,,,\n\r ' + + 'FTE Contract 1 ,01/' + fteYear + ', 02/' + fteYear + ', 03/' + fteYear + ', 04/' + fteYear + ', 05/' + fteYear + ', 06/' + fteYear + ', 07/' + fteYear + + ', 08/' + fteYear + ', 09/' + fteYear + ', 10/' + fteYear + ', 11/' + fteYear + ', 12/' + fteYear + '\n\r' + + buildDataRow('Other Employee', fteMonth, 5.0) + + buildDataRow('FTE Employee', fteMonth, 7.0) + + buildDataRow('Yyy Employee', fteMonth, 4.0); + + FTECsvUploadController csvUpload = new FTECsvUploadController(); + + Test.startTest(); + csvUpload.fileContent = Blob.valueOf(correctData); + csvUpload.fileName = 'Test file name'; + + csvUpload.parseCsvFile(); + Database.executeBatch(new FTETimeAllocator(true)); + Test.stopTest(); + // FTE Employee SFDC_Employee__c employee = [SELECT Id FROM SFDC_Employee__c WHERE Name = 'FTE Employee']; Id contractId = [SELECT Id FROM DContract__c WHERE Name = 'FTE Contract 1'].Id; - FTETimeManager timeManager = new FTETimeManager(employee, fteYear); - timeManager.loadEmployeeTime(); + FTEDataManager timeManager = new FTEDataManager(fteYear, employee.Id); + timeManager.loadEmployeeTime(); + timeManager.loadTemplateTime(); System.assertEquals(56, timeManager.assignedMap.get(contractId).hoursArray[fteMonth - 1]); - timeManager.assignedMap.get(contractId).calculateDays(); + timeManager.assignedMap.get(contractId).calculateDaysAndTotal(); System.assertEquals(7, timeManager.assignedMap.get(contractId).daysArray[fteMonth - 1]); + System.assertEquals(7, timeManager.assignedMap.get(contractId).templateArray[fteMonth - 1]); + assertTemplatesValue(-1, new Set { fteMonth }, timeManager.assignedMap.get(contractId)); + // FTE Employee employee = [SELECT Id FROM SFDC_Employee__c WHERE Name = 'Other Employee']; - timeManager = new FTETimeManager(employee, fteYear); + timeManager = new FTEDataManager(fteYear, employee.Id); timeManager.loadEmployeeTime(); + timeManager.loadTemplateTime(); System.assertEquals(40, timeManager.assignedMap.get(contractId).hoursArray[fteMonth - 1]); - timeManager.assignedMap.get(contractId).calculateDays(); + timeManager.assignedMap.get(contractId).calculateDaysAndTotal(); System.assertEquals(5, timeManager.assignedMap.get(contractId).daysArray[fteMonth - 1]); + System.assertEquals(5, timeManager.assignedMap.get(contractId).templateArray[fteMonth - 1]); + assertTemplatesValue(-1, new Set { fteMonth }, timeManager.assignedMap.get(contractId)); + // FTE Employee employee = [SELECT Id FROM SFDC_Employee__c WHERE Name = 'Yyy Employee']; - timeManager = new FTETimeManager(employee, fteYear); + timeManager = new FTEDataManager(fteYear, employee.Id); timeManager.loadEmployeeTime(); + timeManager.loadTemplateTime(); System.assertEquals(32, timeManager.assignedMap.get(contractId).hoursArray[fteMonth - 1]); - timeManager.assignedMap.get(contractId).calculateDays(); + timeManager.assignedMap.get(contractId).calculateDaysAndTotal(); System.assertEquals(4, timeManager.assignedMap.get(contractId).daysArray[fteMonth - 1]); + System.assertEquals(4, timeManager.assignedMap.get(contractId).templateArray[fteMonth - 1]); + assertTemplatesValue(-1, new Set { fteMonth }, timeManager.assignedMap.get(contractId)); } @isTest public static void shouldUploadAndMoveCSVDataSample2() { Integer fteYear = Date.today().year(); - integer fteMonth = Date.today().month(); + Integer fteMonth = Date.today().month(); String correctData = ',,,,,,\n\r ' + 'FTE Contract 1 ,01/' + fteYear + ', 02/' + fteYear + ', 03/' + fteYear + ', 04/' + fteYear + ', 05/' + fteYear + ', 06/' + fteYear + ', 07/' + fteYear + ', 08/' + fteYear + ', 09/' + fteYear + ', 10/' + fteYear + ', 11/' + fteYear + ', 12/' + fteYear + '\n\r' @@ -1076,131 +1591,52 @@ public class FTETrackerTest { csvUpload.fileName = 'Test file name'; csvUpload.parseCsvFile(); + Database.executeBatch(new FTETimeAllocator(true)); Test.stopTest(); - for (FTE_Data_Record_Status__c rec : [SELECT Status__c, Status_Message__c, Line_Number__c, Line_Number_Text__c FROM FTE_Data_Record_Status__c]) { - System.debug('Status Error Record : ' + rec); - } - System.assertEquals(0, [SELECT count() FROM FTE_Data_Record_Status__c]); - SFDC_Employee__c employee = [SELECT Id FROM SFDC_Employee__c WHERE Name = 'FTE Employee']; Id contractId = [SELECT Id FROM DContract__c WHERE Name = 'FTE Contract 1'].Id; - FTETimeManager timeManager = new FTETimeManager(employee, fteYear); + FTEDataManager timeManager = new FTEDataManager(fteYear, employee.Id); timeManager.loadEmployeeTime(); + timeManager.loadTemplateTime(); System.assertEquals(8, timeManager.assignedMap.get(contractId).hoursArray[fteMonth - 1]); - timeManager.assignedMap.get(contractId).calculateDays(); + timeManager.assignedMap.get(contractId).calculateDaysAndTotal(); System.assertEquals(1, timeManager.assignedMap.get(contractId).daysArray[fteMonth - 1]); System.assertEquals(64.5, timeManager.unassigned.hoursArray[fteMonth - 1]); - timeManager.unassigned.calculateDays(); + timeManager.unassigned.calculateDaysAndTotal(); System.assertEquals(8, timeManager.unassigned.daysArray[fteMonth - 1]); + assertTemplatesValue(-1, new Set { fteMonth }, timeManager.assignedMap.get(contractId)); employee = [SELECT Id FROM SFDC_Employee__c WHERE Name = 'Other Employee']; - timeManager = new FTETimeManager(employee, fteYear); + timeManager = new FTEDataManager(fteYear, employee.Id); timeManager.loadEmployeeTime(); + timeManager.loadTemplateTime(); System.assertEquals(8, timeManager.assignedMap.get(contractId).hoursArray[fteMonth - 1]); - timeManager.assignedMap.get(contractId).calculateDays(); + timeManager.assignedMap.get(contractId).calculateDaysAndTotal(); System.assertEquals(1, timeManager.assignedMap.get(contractId).daysArray[fteMonth - 1]); System.assertEquals(41.9, timeManager.unassigned.hoursArray[fteMonth - 1]); - timeManager.unassigned.calculateDays(); + timeManager.unassigned.calculateDaysAndTotal(); System.assertEquals(5.25, timeManager.unassigned.daysArray[fteMonth - 1]); + assertTemplatesValue(-1, new Set { fteMonth }, timeManager.assignedMap.get(contractId)); employee = [SELECT Id FROM SFDC_Employee__c WHERE Name = 'Yyy Employee']; - timeManager = new FTETimeManager(employee, fteYear); + timeManager = new FTEDataManager(fteYear, employee.Id); timeManager.loadEmployeeTime(); + timeManager.loadTemplateTime(); System.assertEquals(8, timeManager.assignedMap.get(contractId).hoursArray[fteMonth - 1]); - timeManager.assignedMap.get(contractId).calculateDays(); + timeManager.assignedMap.get(contractId).calculateDaysAndTotal(); System.assertEquals(1, timeManager.assignedMap.get(contractId).daysArray[fteMonth - 1]); System.assertEquals(25.1, timeManager.unassigned.hoursArray[fteMonth - 1]); - timeManager.unassigned.calculateDays(); + timeManager.unassigned.calculateDaysAndTotal(); System.assertEquals(3.25, timeManager.unassigned.daysArray[fteMonth - 1]); + assertTemplatesValue(-1, new Set { fteMonth }, timeManager.assignedMap.get(contractId)); } - @isTest - public static void shouldRetunrYearList() { - List years = FTETrackerHelper.getYearsData(); - System.assertEquals(4, years.size()); - Integer currentYear = Date.today().year(); - System.assertEquals(String.valueOf(currentYear + 2), years.get(0).getValue()); - System.assertEquals(String.valueOf(currentYear + 1), years.get(1).getValue()); - System.assertEquals(String.valueOf(currentYear), years.get(2).getValue()); - System.assertEquals(String.valueOf(currentYear - 1), years.get(3).getValue()); - } - @isTest - public static void testTimeManagerEmpl1() { - SFDC_Employee__c employee = [SELECT Id FROM SFDC_Employee__c WHERE Name = 'FTE Employee' LIMIT 1]; - Id contract1Id = [SELECT Id FROM DContract__c WHERE Name = 'FTE Contract 1' LIMIT 1].Id; - Id contract2Id = [SELECT Id FROM DContract__c WHERE Name = 'FTE Contract 2' LIMIT 1].Id; - Id contract3Id = [SELECT Id FROM DContract__c WHERE Name = 'FTE Contract 3' LIMIT 1].Id; - Id contract4Id = [SELECT Id FROM DContract__c WHERE Name = 'FTE Contract 4' LIMIT 1].Id; - - Test.startTest(); - FTETimeManager timeManager = new FTETimeManager(employee, Date.today().year()); - timeManager.loadEmployeeTime(); - System.assertEquals(25.6, timeManager.assignedMap.get(contract1Id).hoursArray[12]); - System.assertEquals(25.6, timeManager.assignedMap.get(contract2Id).hoursArray[12]); - System.assertEquals(10.3, timeManager.assignedMap.get(contract3Id).hoursArray[12]); - System.assertEquals(30.6, timeManager.assignedMap.get(contract4Id).hoursArray[12]); - System.assertEquals(19.3 + 27.6, timeManager.unassigned.hoursArray[12]); - - System.assertEquals(0, [SELECT Id FROM Time_Card__c WHERE Employee__c =: employee.Id AND FTE_Contract__r.FTE_Tracker__c = 'No'].size()); - System.assertEquals(0, [SELECT Id FROM Time_Card__c WHERE Employee__c =: employee.Id AND FTE_Contract__r.FTE_Tracker__c = 'Yes'].size()); - - timeManager.moveTimeToUnassigned(24, Date.today().month(), contract1Id); - System.assertEquals(1, [SELECT Id FROM Time_Card__c WHERE Client__c =: contract1Id AND Employee__c =: employee.Id AND FTE_Contract__r.FTE_Tracker__c = 'No'].size()); - - timeManager.moveTimeToAssigned(8, Date.today().month(), contract3Id); - System.assertEquals(1, [SELECT Id FROM Time_Card__c WHERE FTE_Contract__c =: contract3Id AND Employee__c =: employee.Id AND FTE_Contract__r.FTE_Tracker__c = 'Yes'].size()); - timeManager.moveTimeToAssigned(2, Date.today().month(), contract3Id); - System.assertEquals(1, [SELECT Id FROM Time_Card__c WHERE FTE_Contract__c =: contract3Id AND Employee__c =: employee.Id AND FTE_Contract__r.FTE_Tracker__c = 'Yes'].size()); - - timeManager.moveTimeToUnassigned(5, Date.today().month(), contract3Id); - System.assertEquals(1, [SELECT Id FROM Time_Card__c WHERE FTE_Contract__c =: contract3Id AND Employee__c =: employee.Id AND FTE_Contract__r.FTE_Tracker__c = 'Yes'].size()); - System.assertEquals(1, [SELECT Id FROM Time_Card__c WHERE FTE_Contract__c =: contract3Id AND Employee__c =: employee.Id AND FTE_Contract__r.FTE_Tracker__c = 'Yes' - AND FTE_Hours__c = 5].size()); - Test.stopTest(); - } - - @isTest - public static void testTimeManagerEmpl2() { - SFDC_Employee__c employee = [SELECT Id FROM SFDC_Employee__c WHERE Name = 'Other Employee' LIMIT 1]; - Id contract1Id = [SELECT Id FROM DContract__c WHERE Name = 'FTE Contract 1' LIMIT 1].Id; - Id contract2Id = [SELECT Id FROM DContract__c WHERE Name = 'FTE Contract 2' LIMIT 1].Id; - Id contract3Id = [SELECT Id FROM DContract__c WHERE Name = 'FTE Contract 3' LIMIT 1].Id; - Id contract4Id = [SELECT Id FROM DContract__c WHERE Name = 'FTE Contract 4' LIMIT 1].Id; - - Test.startTest(); - FTETimeManager timeManager = new FTETimeManager(employee, Date.today().year()); - timeManager.loadEmployeeTime(); - System.assertEquals(28.6 - 5, timeManager.assignedMap.get(contract1Id).hoursArray[12]); - System.assertEquals(null, timeManager.assignedMap.get(contract2Id)); - System.assertEquals(null, timeManager.assignedMap.get(contract3Id)); - System.assertEquals(5 - 2, timeManager.assignedMap.get(contract4Id).hoursArray[12]); - System.assertEquals(19.3 + 5 + 2, timeManager.unassigned.hoursArray[12]); - Test.stopTest(); - } - - @isTest - public static void testTimeManagerEmpl3() { - SFDC_Employee__c employee = [SELECT Id FROM SFDC_Employee__c WHERE Name = 'Yyy Employee' LIMIT 1]; - Id contract1Id = [SELECT Id FROM DContract__c WHERE Name = 'FTE Contract 1' LIMIT 1].Id; - Id contract2Id = [SELECT Id FROM DContract__c WHERE Name = 'FTE Contract 2' LIMIT 1].Id; - Id contract3Id = [SELECT Id FROM DContract__c WHERE Name = 'FTE Contract 3' LIMIT 1].Id; - Id contract4Id = [SELECT Id FROM DContract__c WHERE Name = 'FTE Contract 4' LIMIT 1].Id; - Test.startTest(); - FTETimeManager timeManager = new FTETimeManager(employee, Date.today().year()); - timeManager.loadEmployeeTime(); - System.assertEquals(19.1 - 8, timeManager.assignedMap.get(contract1Id).hoursArray[12]); - System.assertEquals(16 - 7 + 5, timeManager.assignedMap.get(contract2Id).hoursArray[12]); - System.assertEquals(7 - 5 + 3 + 16, timeManager.assignedMap.get(contract3Id).hoursArray[12]); - System.assertEquals(8 - 3 + 2, timeManager.assignedMap.get(contract4Id).hoursArray[12]); - System.assertEquals(3 + 22 + 8 + 7 - 16 - 2, timeManager.unassigned.hoursArray[12]); - Test.stopTest(); - } /** * Controller tests @@ -1210,8 +1646,9 @@ public class FTETrackerTest { Integer currYear = Date.today().year(); List workCards = new List(); for (SFDC_Employee__c empl : [SELECT Id FROM SFDC_Employee__c]) { - workCards.add(new FTE_Work_Card__c(Employee__c = empl.Id, Month_1__c = 1, Month_2__c = 2, Month_3__c = 3, Month_4__c = 4, Month_5__c = 5, Month_6__c = 6, Month_7__c = 7, Month_8__c = 8, - Month_9__c = 9, Month_10__c = 10, Month_11__c = 11, Month_12__c = 12, Total__c = 78, Total_Hours__c = 78 * 8, Year__c = currYear)); + workCards.add(new FTE_Work_Card__c(Employee__c = empl.Id, Month_1__c = 1, Month_2__c = 2, Month_3__c = 3, Month_4__c = 22, Month_5__c = 5, Month_6__c = 6, Month_7__c = 7, Month_8__c = 8, + Month_9__c = 9, Month_10__c = 10, Month_11__c = 11, Month_12__c = 12, Total__c = 78, Total_Hours__c = 78 * 8, Year__c = currYear, + Month_Future_9__c = true, Month_Blocked_4__c = true, Month_Future_5__c = true, Month_Blocked_5__c = true)); } insert workCards; @@ -1283,6 +1720,343 @@ public class FTETrackerTest { Test.stopTest(); } + @isTest + public static void testEmployeeCtrl() { + String empId = [SELECT Id FROM SFDC_Employee__c WHERE Name = 'FTE Employee' LIMIT 1].Id; + String contrId = [SELECT Id FROM DContract__c WHERE Name = 'FTE Contract 1' LIMIT 1].Id; + PageReference pageRef = Page.FTE_Employee_View; + pageRef.getParameters().put('employeeId', EncodingUtil.urlDecode(empId, 'UTF-8')); + pageRef.getParameters().put('fteYear', EncodingUtil.urlDecode(String.valueOf(Date.today().year()), 'UTF-8')); + Test.setCurrentPage(pageRef); + + Test.startTest(); // other logic was tested before, we want only test moving to other FTE views + FTEEmployeeController emplCtrl = new FTEEmployeeController(); + + PageReference pageRefTest = emplCtrl.goToEmployeeListView(); + System.assertNotEquals(null, pageRefTest); + System.assertEquals(null, pageRefTest.getParameters().get('fteYear')); //same year we don't need to pass year parameter + System.debug('pageRefTest.getUrl() - ' + pageRefTest.getUrl()); + System.assert(pageRefTest.getUrl().startsWith('/apex/fte_employee_list_view')); + + pageRefTest = emplCtrl.goToProjectListView(); + System.assertNotEquals(null, pageRefTest); + System.assert(pageRefTest.getUrl().startsWith('/apex/fte_project_list_view')); + + emplCtrl.contractId = contrId; + pageRefTest = emplCtrl.goToIndividualProjectView(); + System.assertNotEquals(null, pageRefTest); + System.assertEquals(String.valueOf(Date.today().year()), pageRefTest.getParameters().get('fteYear')); + System.assertEquals(contrId, pageRefTest.getParameters().get('contractId')); + System.assert(pageRefTest.getUrl().startsWith('/apex/fte_individual_project_view')); + + emplCtrl.exportMonth = 2; + pageRefTest = emplCtrl.goToTimeCardView(); + System.assertEquals(String.valueOf(Date.today().year()), pageRefTest.getParameters().get('fteYear')); + System.assertEquals(empId, pageRefTest.getParameters().get('employeeId')); + System.assertEquals('2', pageRefTest.getParameters().get('month')); + System.assert(pageRefTest.getUrl().startsWith('/apex/fte_time_card_view')); + Test.stopTest(); + } + + @isTest + public static void shouldGenerateEmployeeHoursEmplCtrl() { + Date currentDate = Date.today(); + SFDC_Employee__c employee = [SELECT Id FROM SFDC_Employee__c WHERE Name = 'FTE Employee']; + PageReference pageRef = Page.FTE_Employee_View; + pageRef.getParameters().put('employeeId', String.valueOf(employee.Id)); + Test.setCurrentPage(pageRef); + + Test.startTest(); + FTEEmployeeController controller = new FTEEmployeeController(); + controller.initFteEmployeeView(); + Test.stopTest(); + + System.assertEquals(46.9, controller.fteTimeManager.unassigned.hoursArray[currentDate.month() - 1]); + System.assertEquals(5.75, controller.fteTimeManager.unassigned.daysArray[currentDate.month() - 1]); + System.assertEquals(92.1, controller.totalAssignedDays.hoursArray[currentDate.month() - 1]); + System.assertEquals(11.5, controller.totalAssignedDays.daysArray[currentDate.month() - 1]); + } + + @isTest + public static void shouldMoveHoursToUnassignedEmplCtrl() { + Date currentDate = Date.today(); + addTimeCard([SELECT Id FROM DContract__c WHERE Name = 'FTE Contract 1' LIMIT 1].Id, + [SELECT Id FROM SFDC_Employee__c WHERE Name = 'Other Employee'].Id, + Date.newInstance(currentDate.year(), currentDate.month(), 5), 3.3); + SFDC_Employee__c employee = [SELECT Id FROM SFDC_Employee__c WHERE Name = 'FTE Employee']; + + insert new FTE_Work_Card__c(Employee__c = employee.Id, Year__c = Date.today().year(), Month_1__c = 0, + Month_2__c = 0, Month_3__c = 0, Month_4__c = 0, Month_5__c = 0, Month_6__c = 0, + Month_7__c = 0, Month_8__c = 0, Month_9__c = 0, Month_10__c = 0, Month_11__c = 0, + Month_12__c = 0, Total__c = 0, Total_Hours__c = 0); // should be updated + + PageReference pageRef = Page.FTE_Employee_View; + pageRef.getParameters().put('employeeId', String.valueOf(employee.Id)); + Test.setCurrentPage(pageRef); + + Test.startTest(); + FTEEmployeeController controller = new FTEEmployeeController(); + controller.initFteEmployeeView(); + controller.employeeMonth = currentDate.month(); + controller.contractId = [SELECT Id FROM DContract__c WHERE Name = 'FTE Contract 2' LIMIT 1].Id; + controller.fteDays = '3'; + + controller.unassignTime(); + controller.initFteEmployeeView(); + Test.stopTest(); + + System.assertEquals(70.9, controller.fteTimeManager.unassigned.hoursArray[currentDate.month() - 1]); + System.assertEquals(8.75, controller.fteTimeManager.unassigned.daysArray[currentDate.month() - 1]); + System.assertEquals(68.1, controller.totalAssignedDays.hoursArray[currentDate.month() - 1]); + System.assertEquals(8.5, controller.totalAssignedDays.daysArray[currentDate.month() - 1]); + assertWorkCardMonthAndTotal(currentDate.month(), employee.Id, 8.5, 8.5, false, false); + } + + @isTest + public static void shouldMoveHoursFromUnassignedEmplCtrl() { + Date currentDate = Date.today(); + SFDC_Employee__c employee = [SELECT Id FROM SFDC_Employee__c WHERE Name = 'FTE Employee']; + + insert new FTE_Work_Card__c(Employee__c = employee.Id, Year__c = Date.today().year(), Month_1__c = 0, + Month_2__c = 0, Month_3__c = 0, Month_4__c = 0, Month_5__c = 0, Month_6__c = 0, + Month_7__c = 0, Month_8__c = 0, Month_9__c = 0, Month_10__c = 0, Month_11__c = 0, + Month_12__c = 0, Total__c = 0, Total_Hours__c = 0); // should be updated + + PageReference pageRef = Page.FTE_Employee_View; + pageRef.getParameters().put('employeeId', String.valueOf(employee.Id)); + Test.setCurrentPage(pageRef); + + Test.startTest(); + FTEEmployeeController controller = new FTEEmployeeController(); + controller.initFteEmployeeView(); + controller.employeeMonth = currentDate.month(); + controller.fteDays = '2.5'; + controller.selectedFteContract = [SELECT Id FROM DContract__c WHERE Name = 'FTE Contract 1' LIMIT 1].Id; + + controller.assignTime(); + controller.initFteEmployeeView(); + Test.stopTest(); + + System.assertEquals(26.9, controller.fteTimeManager.unassigned.hoursArray[currentDate.month() - 1]); + System.assertEquals(3.25, controller.fteTimeManager.unassigned.daysArray[currentDate.month() - 1]); + System.assertEquals(112.1, controller.totalAssignedDays.hoursArray[currentDate.month() - 1]); + System.assertEquals(14, controller.totalAssignedDays.daysArray[currentDate.month() - 1]); + assertWorkCardMonthAndTotal(currentDate.month(), employee.Id, 14, 14, false, false); + } + + @isTest + public static void shouldSetBlockedTemplateEmplCtrl() { + Date currentDate = Date.today(); + SFDC_Employee__c employee = [SELECT Id FROM SFDC_Employee__c WHERE Name = 'FTE Employee']; + + insert new FTE_Work_Card__c(Employee__c = employee.Id, Year__c = Date.today().year(), Month_1__c = 0, + Month_2__c = 0, Month_3__c = 0, Month_4__c = 0, Month_5__c = 0, Month_6__c = 0, + Month_7__c = 0, Month_8__c = 0, Month_9__c = 0, Month_10__c = 0, Month_11__c = 0, + Month_12__c = 0, Total__c = 0, Total_Hours__c = 0); // should be updated + + PageReference pageRef = Page.FTE_Employee_View; + pageRef.getParameters().put('employeeId', String.valueOf(employee.Id)); + Test.setCurrentPage(pageRef); + + Test.startTest(); + FTEEmployeeController controller = new FTEEmployeeController(); + controller.initFteEmployeeView(); + controller.employeeMonth = currentDate.month(); + controller.fteDays = '6.00'; + controller.contractId = [SELECT Id FROM DContract__c WHERE Name = 'FTE Contract 1' LIMIT 1].Id; + + controller.setThreshold(); + controller.initFteEmployeeView(); + Test.stopTest(); + + System.assertEquals(48, controller.fteTimeManager.assignedMap.get(controller.contractId).hoursArray[currentDate.month() - 1]); + System.assertEquals(24.5, controller.fteTimeManager.unassigned.hoursArray[currentDate.month() - 1]); + System.assertEquals(114.5, controller.totalAssignedDays.hoursArray[currentDate.month() - 1]); + System.assertEquals(14.25, controller.totalAssignedDays.daysArray[currentDate.month() - 1]); + System.assertEquals(6, controller.fteTimeManager.assignedMap.get(controller.contractId).templateArray[currentDate.month() - 1]); + System.assertEquals('fteCell blocked', controller.fteTimeManager.assignedMap.get(controller.contractId).cssStyle[currentDate.month() - 1]); + assertTemplatesValue(-1, new Set { currentDate.month() }, controller.fteTimeManager.assignedMap.get(controller.contractId)); + + List futureTemplates = [SELECT Contract__c, Contract__r.Name, Employee__c, Month_1__c, Month_2__c, Month_3__c, Month_4__c, + Month_5__c, Month_6__c, Month_7__c, Month_8__c, Month_9__c, Month_10__c, Month_11__c, Month_12__c + FROM FTE_Data_Record__c]; + System.assertEquals(1, futureTemplates.size()); + assertWorkCardMonthAndTotal(currentDate.month(), employee.Id, 14.25, 14.25, false, true); + } + + @isTest + public static void shouldSetFutureTemplateEmplCtrl() { + Date currentDate = Date.today(); + SFDC_Employee__c employee = [SELECT Id FROM SFDC_Employee__c WHERE Name = 'FTE Employee']; + + insert new FTE_Work_Card__c(Employee__c = employee.Id, Year__c = Date.today().year(), Month_1__c = 0, + Month_2__c = 0, Month_3__c = 0, Month_4__c = 0, Month_5__c = 0, Month_6__c = 0, + Month_7__c = 0, Month_8__c = 0, Month_9__c = 0, Month_10__c = 0, Month_11__c = 0, + Month_12__c = 0, Total__c = 0, Total_Hours__c = 0); // should be updated + + PageReference pageRef = Page.FTE_Employee_View; + pageRef.getParameters().put('employeeId', String.valueOf(employee.Id)); + Test.setCurrentPage(pageRef); + + Test.startTest(); + FTEEmployeeController controller = new FTEEmployeeController(); + controller.initFteEmployeeView(); + controller.employeeMonth = currentDate.month(); + controller.fteDays = '12.00'; + controller.contractId = [SELECT Id FROM DContract__c WHERE Name = 'FTE Contract 1' LIMIT 1].Id; + + controller.setThreshold(); + controller.initFteEmployeeView(); + Test.stopTest(); + + System.assertEquals(72.5, controller.fteTimeManager.assignedMap.get(controller.contractId).hoursArray[currentDate.month() - 1]); // no 96 hours contains orginal time for contracts + System.assertEquals(12, controller.fteTimeManager.assignedMap.get(controller.contractId).daysArray[currentDate.month() - 1]); // no 96 hours contains orginal time for contracts + System.assertEquals(0, controller.fteTimeManager.unassigned.hoursArray[currentDate.month() - 1]); + // in merged records like totals and in hours we have merged data with templates, + // we don't do this in other hours array like unassigned assignedMap and unassignedMap because we are using them for calculations when moving time + System.assertEquals(162.5, controller.totalAssignedDays.hoursArray[currentDate.month() - 1]); + System.assertEquals(20.25, controller.totalAssignedDays.daysArray[currentDate.month() - 1]); + System.assertEquals(12, controller.fteTimeManager.assignedMap.get(controller.contractId).templateArray[currentDate.month() - 1]); + System.assertEquals('fteCell future', controller.fteTimeManager.assignedMap.get(controller.contractId).cssStyle[currentDate.month() - 1]); + assertTemplatesValue(-1, new Set { currentDate.month() }, controller.fteTimeManager.assignedMap.get(controller.contractId)); + + List futureTemplates = [SELECT Contract__c, Contract__r.Name, Employee__c, Month_1__c, Month_2__c, Month_3__c, Month_4__c, + Month_5__c, Month_6__c, Month_7__c, Month_8__c, Month_9__c, Month_10__c, Month_11__c, Month_12__c + FROM FTE_Data_Record__c]; + System.assertEquals(1, futureTemplates.size()); + assertWorkCardMonthAndTotal(currentDate.month(), employee.Id, 20.25, 20.25, true, false); + } + + @isTest + public static void shouldUpdateTemplateEmplCtrl() { + Date currentDate = Date.today(); + SFDC_Employee__c employee = [SELECT Id FROM SFDC_Employee__c WHERE Name = 'FTE Employee']; + + insert new FTE_Work_Card__c(Employee__c = employee.Id, Year__c = Date.today().year(), Month_1__c = 0, + Month_2__c = 0, Month_3__c = 0, Month_4__c = 0, Month_5__c = 0, Month_6__c = 0, + Month_7__c = 0, Month_8__c = 0, Month_9__c = 0, Month_10__c = 0, Month_11__c = 0, + Month_12__c = 0, Total__c = 0, Total_Hours__c = 0); // should be updated + + Id testContract = [SELECT Id FROM DContract__c WHERE Name = 'FTE Contract 1' LIMIT 1].Id; + FTE_Data_Record__c newRecord = new FTE_Data_Record__c(Employee__c = employee.Id, Year__c = currentDate.year(), Contract__c = testContract); + SObject sObjRec = (SObject) newRecord; + sObjRec.put(FTETrackerHelper.getFieldName(currentDate.month()), 6); + insert newRecord; + + PageReference pageRef = Page.FTE_Employee_View; + pageRef.getParameters().put('employeeId', String.valueOf(employee.Id)); + Test.setCurrentPage(pageRef); + + Test.startTest(); + FTEEmployeeController controller = new FTEEmployeeController(); + controller.initFteEmployeeView(); + controller.employeeMonth = currentDate.month(); + controller.fteDays = '12.00'; + controller.contractId = testContract; + + controller.setThreshold(); + controller.initFteEmployeeView(); + Test.stopTest(); + + System.assertEquals(72.5, controller.fteTimeManager.assignedMap.get(controller.contractId).hoursArray[currentDate.month() - 1]); // no 96 hours contains orginal time for contracts + System.assertEquals(12, controller.fteTimeManager.assignedMap.get(controller.contractId).daysArray[currentDate.month() - 1]); // no 96 hours contains orginal time for contracts + System.assertEquals(0, controller.fteTimeManager.unassigned.hoursArray[currentDate.month() - 1]); + // in merged records like totals and in hours we have merged data with templates, + // we don't do this in other hours array like unassigned assignedMap and unassignedMap because we are using them for calculations when moving time + System.assertEquals(162.5, controller.totalAssignedDays.hoursArray[currentDate.month() - 1]); + System.assertEquals(20.25, controller.totalAssignedDays.daysArray[currentDate.month() - 1]); + System.assertEquals(12, controller.fteTimeManager.assignedMap.get(controller.contractId).templateArray[currentDate.month() - 1]); + System.assertEquals('fteCell future', controller.fteTimeManager.assignedMap.get(controller.contractId).cssStyle[currentDate.month() - 1]); + assertTemplatesValue(-1, new Set { currentDate.month() }, controller.fteTimeManager.assignedMap.get(controller.contractId)); + + List futureTemplates = [SELECT Id, Contract__c, Contract__r.Name, Employee__c, Month_1__c, Month_2__c, Month_3__c, Month_4__c, + Month_5__c, Month_6__c, Month_7__c, Month_8__c, Month_9__c, Month_10__c, Month_11__c, Month_12__c + FROM FTE_Data_Record__c]; + System.assertEquals(1, futureTemplates.size()); + System.assertEquals(newRecord.Id, futureTemplates.get(0).Id); + assertWorkCardMonthAndTotal(currentDate.month(), employee.Id, 20.25, 20.25, true, false); + } + + @isTest + public static void shouldSetZeroInTemplateEmplCtrl() { + Date currentDate = Date.today(); + SFDC_Employee__c employee = [SELECT Id FROM SFDC_Employee__c WHERE Name = 'FTE Employee']; + + insert new FTE_Work_Card__c(Employee__c = employee.Id, Year__c = Date.today().year(), Month_1__c = 0, + Month_2__c = 0, Month_3__c = 0, Month_4__c = 0, Month_5__c = 0, Month_6__c = 0, + Month_7__c = 0, Month_8__c = 0, Month_9__c = 0, Month_10__c = 0, Month_11__c = 0, + Month_12__c = 0, Total__c = 0, Total_Hours__c = 0); // should be updated + + PageReference pageRef = Page.FTE_Employee_View; + pageRef.getParameters().put('employeeId', String.valueOf(employee.Id)); + Test.setCurrentPage(pageRef); + + Test.startTest(); + FTEEmployeeController controller = new FTEEmployeeController(); + controller.initFteEmployeeView(); + controller.employeeMonth = currentDate.month(); + controller.fteDays = '0.00'; + controller.contractId = [SELECT Id FROM DContract__c WHERE Name = 'FTE Contract 1' LIMIT 1].Id; + + controller.setThreshold(); + controller.initFteEmployeeView(); + Test.stopTest(); + + System.assertEquals(0, controller.fteTimeManager.assignedMap.get(controller.contractId).hoursArray[currentDate.month() - 1]); + System.assertEquals(72.5, controller.fteTimeManager.unassigned.hoursArray[currentDate.month() - 1]); + System.assertEquals(66.5, controller.totalAssignedDays.hoursArray[currentDate.month() - 1]); + System.assertEquals(8.25, controller.totalAssignedDays.daysArray[currentDate.month() - 1]); + System.assertEquals(0, controller.fteTimeManager.assignedMap.get(controller.contractId).templateArray[currentDate.month() - 1]); + System.assertEquals('fteCell blocked', controller.fteTimeManager.assignedMap.get(controller.contractId).cssStyle[currentDate.month() - 1]); + assertTemplatesValue(-1, new Set { currentDate.month() }, controller.fteTimeManager.assignedMap.get(controller.contractId)); + + List futureTemplates = [SELECT Contract__c, Contract__r.Name, Employee__c, Month_1__c, Month_2__c, Month_3__c, Month_4__c, + Month_5__c, Month_6__c, Month_7__c, Month_8__c, Month_9__c, Month_10__c, Month_11__c, Month_12__c + FROM FTE_Data_Record__c]; + System.assertEquals(1, futureTemplates.size()); + assertWorkCardMonthAndTotal(currentDate.month(), employee.Id, 8.25, 8.25, false, true); + } + + @isTest + public static void shouldResetTemplateEmplCtrl() { + Date currentDate = Date.today(); + SFDC_Employee__c employee = [SELECT Id FROM SFDC_Employee__c WHERE Name = 'FTE Employee']; + + insert new FTE_Work_Card__c(Employee__c = employee.Id, Year__c = Date.today().year(), Month_1__c = 0, + Month_2__c = 0, Month_3__c = 0, Month_4__c = 0, Month_5__c = 0, Month_6__c = 0, + Month_7__c = 0, Month_8__c = 0, Month_9__c = 0, Month_10__c = 0, Month_11__c = 0, + Month_12__c = 0, Total__c = 0, Total_Hours__c = 0); // should be updated + + Id testContract = [SELECT Id FROM DContract__c WHERE Name = 'FTE Contract 1' LIMIT 1].Id; + FTE_Data_Record__c newRecord = new FTE_Data_Record__c(Employee__c = employee.Id, Year__c = currentDate.year(), Contract__c = testContract); + SObject sObjRec = (SObject) newRecord; + sObjRec.put(FTETrackerHelper.getFieldName(currentDate.month()), 6); + insert newRecord; + + PageReference pageRef = Page.FTE_Employee_View; + pageRef.getParameters().put('employeeId', String.valueOf(employee.Id)); + Test.setCurrentPage(pageRef); + + Test.startTest(); + FTEEmployeeController controller = new FTEEmployeeController(); + controller.initFteEmployeeView(); + controller.employeeMonth = currentDate.month(); + controller.fteDays = ''; + controller.contractId = testContract; + + controller.setThreshold(); + controller.initFteEmployeeView(); + Test.stopTest(); + + List futureTemplates = [SELECT Id, Contract__c, Contract__r.Name, Employee__c, Month_1__c, Month_2__c, Month_3__c, Month_4__c, + Month_5__c, Month_6__c, Month_7__c, Month_8__c, Month_9__c, Month_10__c, Month_11__c, Month_12__c + FROM FTE_Data_Record__c]; + System.assertEquals(1, futureTemplates.size()); + System.assertEquals(newRecord.Id, futureTemplates.get(0).Id); + System.assertEquals(-1, (Decimal) ((SObject) futureTemplates.get(0)).get(FTETrackerHelper.getFieldName(currentDate.month()))); + assertWorkCardMonthAndTotal(currentDate.month(), employee.Id, 11.5, 11.5, false, false); + } + @isTest public static void testFileCtrl() { String empId = [SELECT Id FROM SFDC_Employee__c WHERE Name = 'FTE Employee' LIMIT 1].Id; @@ -1342,42 +2116,256 @@ public class FTETrackerTest { Test.stopTest(); } + + + + /** + * Utils tests + */ @isTest - public static void testEmployeeCtrl() { - String empId = [SELECT Id FROM SFDC_Employee__c WHERE Name = 'FTE Employee' LIMIT 1].Id; - String contrId = [SELECT Id FROM DContract__c WHERE Name = 'FTE Contract 1' LIMIT 1].Id; - PageReference pageRef = Page.FTE_Employee_View; - pageRef.getParameters().put('employeeId', EncodingUtil.urlDecode(empId, 'UTF-8')); - pageRef.getParameters().put('fteYear', EncodingUtil.urlDecode(String.valueOf(Date.today().year()), 'UTF-8')); - Test.setCurrentPage(pageRef); + public static void testIsContractFTE() { + Id fte1 = [SELECT Id FROM DContract__c WHERE Name = 'FTE Contract 1' LIMIT 1].Id; + Id fte2 = [SELECT Id FROM DContract__c WHERE Name = 'FTE Contract 4' LIMIT 1].Id; + Id noFte1 = [SELECT Id FROM DContract__c WHERE Name = 'Unassigned Contract 1' LIMIT 1].Id; + Id noFte2 = [SELECT Id FROM DContract__c WHERE Name = 'Unassigned Contract 2' LIMIT 1].Id; + System.assertEquals(true, FTETrackerHelper.isContractFTE(fte1)); + System.assertEquals(true, FTETrackerHelper.isContractFTE(fte2)); + System.assertEquals(false, FTETrackerHelper.isContractFTE(noFte1)); + System.assertEquals(false, FTETrackerHelper.isContractFTE(noFte2)); + } - Test.startTest(); // other logic was tested before, we want only test moving to other FTE views - FTEEmployeeController emplCtrl = new FTEEmployeeController(); + @isTest + public static void shouldRetunrYearList() { + List years = FTETrackerHelper.getYearsData(); + System.assertEquals(4, years.size()); + Integer currentYear = Date.today().year(); + System.assertEquals(String.valueOf(currentYear + 2), years.get(0).getValue()); + System.assertEquals(String.valueOf(currentYear + 1), years.get(1).getValue()); + System.assertEquals(String.valueOf(currentYear), years.get(2).getValue()); + System.assertEquals(String.valueOf(currentYear - 1), years.get(3).getValue()); + } - PageReference pageRefTest = emplCtrl.goToEmployeeListView(); - System.assertNotEquals(null, pageRefTest); - System.assertEquals(null, pageRefTest.getParameters().get('fteYear')); //same year we don't need to pass year parameter - System.debug('pageRefTest.getUrl() - ' + pageRefTest.getUrl()); - System.assert(pageRefTest.getUrl().startsWith('/apex/fte_employee_list_view')); + @isTest + public static void shouldBuildCorrectFieldNames() { + System.assertEquals('Month_Updated_4__c', FTETrackerHelper.getFieldUpdatedName(4)); + System.assertEquals('Month_Updated_11__c', FTETrackerHelper.getFieldUpdatedName(11)); + System.assertEquals('Month_Blocked_4__c', FTETrackerHelper.getFieldTemplateBlockedName(4)); + System.assertEquals('Month_Blocked_7__c', FTETrackerHelper.getFieldTemplateBlockedName(7)); + System.assertEquals('Month_Future_9__c', FTETrackerHelper.getFieldTemplateFutureName(9)); + System.assertEquals('Month_Future_7__c', FTETrackerHelper.getFieldTemplateFutureName(7)); + System.assertEquals('Month_6__c', FTETrackerHelper.getFieldName(6)); + System.assertEquals('Month_10__c', FTETrackerHelper.getFieldName(10)); + } - pageRefTest = emplCtrl.goToProjectListView(); - System.assertNotEquals(null, pageRefTest); - System.assert(pageRefTest.getUrl().startsWith('/apex/fte_project_list_view')); + @isTest + public static void shouldRoundHoursToDays() { + System.assertEquals(0.25, FTETrackerHelper.roundtoDays(1)); + System.assertEquals(0.25, FTETrackerHelper.roundtoDays(2)); + System.assertEquals(0.5, FTETrackerHelper.roundtoDays(3)); + System.assertEquals(0.5, FTETrackerHelper.roundtoDays(4)); + System.assertEquals(0.75, FTETrackerHelper.roundtoDays(5)); + System.assertEquals(0.75, FTETrackerHelper.roundtoDays(6)); + System.assertEquals(1, FTETrackerHelper.roundtoDays(7)); + System.assertEquals(1, FTETrackerHelper.roundtoDays(8)); + System.assertEquals(1.25, FTETrackerHelper.roundtoDays(9)); + System.assertEquals(1.25, FTETrackerHelper.roundtoDays(10)); + System.assertEquals(1.5, FTETrackerHelper.roundtoDays(11)); + System.assertEquals(1.5, FTETrackerHelper.roundtoDays(12)); + System.assertEquals(1.75, FTETrackerHelper.roundtoDays(13)); + System.assertEquals(1.75, FTETrackerHelper.roundtoDays(14)); + System.assertEquals(0.25, FTETrackerHelper.roundtoDays(1.5)); + System.assertEquals(0.25, FTETrackerHelper.roundtoDays(2.5)); + System.assertEquals(0.5, FTETrackerHelper.roundtoDays(3.5)); + System.assertEquals(0.5, FTETrackerHelper.roundtoDays(4.5)); + System.assertEquals(0.75, FTETrackerHelper.roundtoDays(5.5)); + System.assertEquals(0.75, FTETrackerHelper.roundtoDays(6.5)); + } - emplCtrl.contractId = contrId; - pageRefTest = emplCtrl.goToIndividualProjectView(); - System.assertNotEquals(null, pageRefTest); - System.assertEquals(String.valueOf(Date.today().year()), pageRefTest.getParameters().get('fteYear')); - System.assertEquals(contrId, pageRefTest.getParameters().get('contractId')); - System.assert(pageRefTest.getUrl().startsWith('/apex/fte_individual_project_view')); + @isTest + public static void testEmployeeDataWrappers() { + List employees = [SELECT Id, Name FROM SFDC_Employee__c WHERE Employee_Status__c = 'Active' ORDER BY Name]; + Id fteEmployee = employees.get(0).Id; + Id otherEmployee = employees.get(1).Id; + Id yyyEmployee = employees.get(2).Id; - emplCtrl.exportMonth = 2; - pageRefTest = emplCtrl.goToTimeCardView(); - System.assertEquals(String.valueOf(Date.today().year()), pageRefTest.getParameters().get('fteYear')); - System.assertEquals(empId, pageRefTest.getParameters().get('employeeId')); - System.assertEquals('2', pageRefTest.getParameters().get('month')); - System.assert(pageRefTest.getUrl().startsWith('/apex/fte_time_card_view')); - Test.stopTest(); + Integer currentYear = Date.today().year(); + Integer pYear = currentYear - 1; + Integer nYear = currentYear + 1; + + Set emplWrappers = new Set(); + emplWrappers.add(new FTEEmployeeWrapper(fteEmployee, currentYear)); + emplWrappers.add(new FTEEmployeeWrapper(otherEmployee, currentYear)); + emplWrappers.add(new FTEEmployeeWrapper(fteEmployee, pYear)); + emplWrappers.add(new FTEEmployeeWrapper(fteEmployee, pYear)); + emplWrappers.add(new FTEEmployeeWrapper(otherEmployee, currentYear)); + emplWrappers.add(new FTEEmployeeWrapper(fteEmployee, nYear)); + emplWrappers.add(new FTEEmployeeWrapper(fteEmployee, currentYear)); + emplWrappers.add(new FTEEmployeeWrapper(fteEmployee, nYear)); + + System.assertEquals(4, emplWrappers.size()); + + Set emplMonthWrappers = new Set(); + emplMonthWrappers.add(new FTEEmployeeMonthWrapper(fteEmployee, 1, currentYear)); + emplMonthWrappers.add(new FTEEmployeeMonthWrapper(otherEmployee, 1, currentYear)); + emplMonthWrappers.add(new FTEEmployeeMonthWrapper(fteEmployee, 1, pYear)); + emplMonthWrappers.add(new FTEEmployeeMonthWrapper(fteEmployee, 1, pYear)); + emplMonthWrappers.add(new FTEEmployeeMonthWrapper(otherEmployee, 1, currentYear)); + emplMonthWrappers.add(new FTEEmployeeMonthWrapper(fteEmployee, 1, nYear)); + emplMonthWrappers.add(new FTEEmployeeMonthWrapper(fteEmployee, 1, currentYear)); + emplMonthWrappers.add(new FTEEmployeeMonthWrapper(fteEmployee, 1, nYear)); + emplMonthWrappers.add(new FTEEmployeeMonthWrapper(fteEmployee, 7, nYear)); + emplMonthWrappers.add(new FTEEmployeeMonthWrapper(otherEmployee, 2, nYear)); + emplMonthWrappers.add(new FTEEmployeeMonthWrapper(fteEmployee, 3, nYear)); + + System.assertEquals(7, emplMonthWrappers.size()); + List sortedList = new List(); + sortedList.addAll(emplMonthWrappers); + sortedList.sort(); + + System.assertEquals(7, sortedList.size()); + System.assertEquals(sortedList.get(0).getEmployeeId(), sortedList.get(1).getEmployeeId()); + System.assertEquals(sortedList.get(5).getEmployeeId(), sortedList.get(6).getEmployeeId()); + } + + @isTest + public static void testEmployeeTimeDays() { + FTEEmployeeTime emplTime = new FTEEmployeeTime(null, null); + for (Integer i = 0; i < 12; i++) { + emplTime.hoursArray[i] = (i + 1) * 2; + } + emplTime.templateArray[4] = 2; + emplTime.templateArray[5] = 3; + emplTime.calculateDaysAndTotal(); + + System.assert(!emplTime.merged); + System.assertEquals(0.25, emplTime.daysArray[0]); + System.assertEquals(0.5, emplTime.daysArray[1]); + System.assertEquals(0.75, emplTime.daysArray[2]); + System.assertEquals(1, emplTime.daysArray[3]); + System.assertEquals(2, emplTime.daysArray[4]); + System.assertEquals(3, emplTime.daysArray[5]); + } + + @isTest + public static void testEmployeeTimeMerge() { + FTEEmployeeTime emplTime1 = new FTEEmployeeTime(null, null); + FTEEmployeeTime emplTime2 = new FTEEmployeeTime(null, null); + FTEEmployeeTime emplTime3 = new FTEEmployeeTime(null, null); + for (Integer i = 0; i < 12; i++) { + emplTime1.hoursArray[i] = i + 1; + emplTime2.hoursArray[i] = i + 3; + emplTime3.hoursArray[i] = i + 2; + } + + emplTime1.templateArray[4] = 2; + emplTime3.templateArray[4] = 3; + emplTime3.templateArray[1] = 4; + + FTEEmployeeTime mergedData = new FTEEmployeeTime(null, null); + mergedData.mergeEmployeeTime(emplTime1); + System.assert(mergedData.merged); + System.assertEquals(1, mergedData.hoursArray[0]); + System.assertEquals(2, mergedData.hoursArray[1]); + System.assertEquals(3, mergedData.hoursArray[2]); + System.assertEquals(4, mergedData.hoursArray[3]); + System.assertEquals(16, mergedData.hoursArray[4]); + System.assertEquals(-1, mergedData.templateArray[0]); + System.assertEquals(-1, mergedData.templateArray[1]); + System.assertEquals(-1, mergedData.templateArray[2]); + System.assertEquals(-1, mergedData.templateArray[3]); + System.assertEquals(16, mergedData.templateArray[4]); + + mergedData.mergeEmployeeTime(emplTime2); + System.assertEquals(1 + 3, mergedData.hoursArray[0]); + System.assertEquals(2 + 4, mergedData.hoursArray[1]); + System.assertEquals(3 + 5, mergedData.hoursArray[2]); + System.assertEquals(4 + 6, mergedData.hoursArray[3]); + System.assertEquals(16 + 7, mergedData.hoursArray[4]); + System.assertEquals(-1, mergedData.templateArray[0]); + System.assertEquals(-1, mergedData.templateArray[1]); + System.assertEquals(-1, mergedData.templateArray[2]); + System.assertEquals(-1, mergedData.templateArray[3]); + System.assertEquals(16 + 7, mergedData.templateArray[4]); + + mergedData.mergeEmployeeTime(emplTime3); + System.assertEquals(1 + 3 + 2, mergedData.hoursArray[0]); + System.assertEquals(2 + 4 + 32, mergedData.hoursArray[1]); + System.assertEquals(3 + 5 + 4, mergedData.hoursArray[2]); + System.assertEquals(4 + 6 + 5, mergedData.hoursArray[3]); + System.assertEquals(16 + 7 + 24, mergedData.hoursArray[4]); + System.assertEquals(-1, mergedData.templateArray[0]); + System.assertEquals(2 + 4 + 32, mergedData.templateArray[1]); + System.assertEquals(-1, mergedData.templateArray[2]); + System.assertEquals(-1, mergedData.templateArray[3]); + System.assertEquals(16 + 7 + 24, mergedData.templateArray[4]); + + mergedData.calculateDaysAndTotal(); + System.assertEquals(0.75, mergedData.daysArray[0]); + System.assertEquals(4.75, mergedData.daysArray[1]); + System.assertEquals(1.5, mergedData.daysArray[2]); + System.assertEquals(2, mergedData.daysArray[3]); + System.assertEquals(6, mergedData.daysArray[4]); + } + + @isTest + public static void testIsWorkingDay() { + System.assertEquals(false, FTETrackerHelper.isWorkingDay(2019, 5, 26)); + System.assertEquals(true, FTETrackerHelper.isWorkingDay(2019, 5, 27)); + System.assertEquals(true, FTETrackerHelper.isWorkingDay(2019, 5, 28)); + System.assertEquals(true, FTETrackerHelper.isWorkingDay(2019, 5, 29)); + System.assertEquals(true, FTETrackerHelper.isWorkingDay(2019, 5, 30)); + System.assertEquals(true, FTETrackerHelper.isWorkingDay(2019, 5, 31)); + System.assertEquals(false, FTETrackerHelper.isWorkingDay(2019, 6, 1)); + System.assertEquals(false, FTETrackerHelper.isWorkingDay(2019, 6, 2)); + System.assertEquals(true, FTETrackerHelper.isWorkingDay(2019, 6, 3)); + } + + + + + /** + * Tests helper methods + */ + private static void assertWorkCardMonthAndTotal(Integer month, Id employeeId, Decimal value, Decimal totalValue, Boolean futureFlag, Boolean blockedFlag) { + List employeeWorkCards = [SELECT Id, Total__c, Month_1__c, Month_2__c, Month_3__c, Month_4__c, Month_5__c, Month_6__c, + Month_7__c, Month_8__c, Month_9__c, Month_10__c, Month_11__c, Month_12__c, + Month_Future_1__c, Month_Future_2__c, Month_Future_3__c, Month_Future_4__c, Month_Future_5__c, Month_Future_6__c, + Month_Future_7__c, Month_Future_8__c, Month_Future_9__c, Month_Future_10__c, Month_Future_11__c, Month_Future_12__c, + Month_Blocked_1__c, Month_Blocked_2__c, Month_Blocked_3__c, Month_Blocked_4__c, Month_Blocked_5__c, Month_Blocked_6__c, + Month_Blocked_7__c, Month_Blocked_8__c, Month_Blocked_9__c, Month_Blocked_10__c, Month_Blocked_11__c, Month_Blocked_12__c + FROM FTE_Work_Card__c WHERE Employee__c =: employeeId AND Year__c =: Date.today().year()]; + System.assertEquals(1, employeeWorkCards.size()); + FTE_Work_Card__c worcCard = employeeWorkCards.get(0); + SObject workCardSObj = (SObject) worcCard; + Decimal cardValue = (Decimal) workCardSObj.get(FTETrackerHelper.getFieldName(month)); + Decimal totalCardValue = (Decimal) workCardSObj.get('Total__c'); + Boolean futureFlagCard = (Boolean) workCardSObj.get(FTETrackerHelper.getFieldTemplateFutureName(month)); + Boolean blockedFlagCard = (Boolean)workCardSObj.get(FTETrackerHelper.getFieldTemplateBlockedName(month)); + + System.assertEquals(value, cardValue); + System.assertEquals(totalValue, totalCardValue); + System.assertEquals(futureFlag, futureFlagCard); + System.assertEquals(blockedFlag, blockedFlagCard); + } + + private static void assertTemplateUpdated(FTE_Data_Record__c template) { + assertTemplateUpdated(template, true, true, true, true, true, true, true, true, true, true, true, true); + } + + private static void assertTemplateUpdated(FTE_Data_Record__c template, Boolean m1, Boolean m2, Boolean m3, Boolean m4, Boolean m5, + Boolean m6, Boolean m7, Boolean m8, Boolean m9, Boolean m10, Boolean m11, Boolean m12) { + System.assertEquals(m1, template.Month_Updated_1__c); + System.assertEquals(m2, template.Month_Updated_2__c); + System.assertEquals(m3, template.Month_Updated_3__c); + System.assertEquals(m4, template.Month_Updated_4__c); + System.assertEquals(m5, template.Month_Updated_5__c); + System.assertEquals(m6, template.Month_Updated_6__c); + System.assertEquals(m7, template.Month_Updated_7__c); + System.assertEquals(m8, template.Month_Updated_8__c); + System.assertEquals(m9, template.Month_Updated_9__c); + System.assertEquals(m10, template.Month_Updated_10__c); + System.assertEquals(m11, template.Month_Updated_11__c); + System.assertEquals(m12, template.Month_Updated_12__c); } private static void assertFTEDataRecord(List elements, String emplName, Integer fteYear, String contractName, @@ -1410,11 +2398,39 @@ public class FTETrackerTest { System.assertEquals(m12, rec.getMonthTime(12)); } + private static void assertFTEDBDataRecord(FTE_Data_Record__c rec, String emplName, Integer fteYear, String contractName, + Decimal m1, Decimal m2, Decimal m3, Decimal m4, Decimal m5, Decimal m6, + Decimal m7, Decimal m8, Decimal m9, Decimal m10, Decimal m11, Decimal m12) { + System.assertEquals(fteYear, rec.Year__c); + System.assertEquals(emplName, rec.Employee__r.Name); + System.assertEquals(contractName, rec.Contract__r.Name); + System.assertEquals(m1, rec.Month_1__c); + System.assertEquals(m2, rec.Month_2__c); + System.assertEquals(m3, rec.Month_3__c); + System.assertEquals(m4, rec.Month_4__c); + System.assertEquals(m5, rec.Month_5__c); + System.assertEquals(m6, rec.Month_6__c); + System.assertEquals(m7, rec.Month_7__c); + System.assertEquals(m8, rec.Month_8__c); + System.assertEquals(m9, rec.Month_9__c); + System.assertEquals(m10, rec.Month_10__c); + System.assertEquals(m11, rec.Month_11__c); + System.assertEquals(m12, rec.Month_12__c); + } + + private static void assertTemplatesValue(Decimal expectedValue, Set monthsToSkip, FTEEmployeeTime emplTime) { + for (Integer month = 1; month <= 12; month++) { + if (!monthsToSkip.contains(month)) { + System.assertEquals(expectedValue, emplTime.templateArray[month -1]); + } + } + } + private static String buildDataRow(String employeeName, Integer month, Decimal value) { String hoursRow = ''; for (Integer i = 1; i <= 12; i++) { if (i != month) { - hoursRow += ',' + ' 0 '; + hoursRow += ',' + ' '; } else { hoursRow += ',' + value; } @@ -1481,4 +2497,16 @@ public class FTETrackerTest { ); return timeCardObj; } + + private static FTE_Data_Record__c buildContractTemplate(Id emplId, Id contractId, Decimal value) { + return buildContractTemplate(emplId, contractId, value, Date.today().year(), Date.today().month()); + } + + private static FTE_Data_Record__c buildContractTemplate(Id emplId, Id contractId, Decimal value, Integer year, Integer month) { + FTE_Data_Record__c template = new FTE_Data_Record__c(Year__c = year, Contract__c = contractId, Employee__c = emplId); + SObject sObj = (SObject) template; + sObj.put(FTETrackerHelper.getFieldName(month), value); + sObj.put(FTETrackerHelper.getFieldUpdatedName(month), true); + return template; + } } \ No newline at end of file diff --git a/src/classes/FTETriggerHelper.cls b/src/classes/FTETriggerHelper.cls new file mode 100644 index 00000000..e98dfc26 --- /dev/null +++ b/src/classes/FTETriggerHelper.cls @@ -0,0 +1,71 @@ +/** + * Helper class to cache FTE Templates and update month flags. + */ +public class FTETriggerHelper { + + public static void processTemplates(List timeCards) { + // we need use maps because in trigger we also calculate time card salary, we don't want exceed limits like 2000 charachters per SOQL query, 200 queries and others + // It is complicated but we want make synchronization error prone + Integer yearMin = Date.today().year(); + Integer yearMax = yearMin; + Set employeeSet = new Set (); + + for (Time_Card__c tc : timeCards) { + employeeSet.add(tc.Employee__c); + Integer tcYear = tc.Date__c.year(); + if (tcYear > yearMax) { + yearMax = tcYear; + } else if (tcYear < yearMin) { + yearMin = tcYear; + } + } + + Map> templatesMapping = new Map>(); + List toUpdate = new List(); + Set fteContracts = new Set(); + for (FTE_Data_Record__c emplTemplate : [SELECT Id, Year__c, Employee__c, Contract__c, Month_Updated_1__c, Month_Updated_2__c, Month_Updated_3__c, Month_Updated_4__c, Month_Updated_5__c, + Month_Updated_6__c, Month_Updated_7__c, Month_Updated_8__c, Month_Updated_9__c, Month_Updated_10__c, Month_Updated_11__c, Month_Updated_12__c + FROM FTE_Data_Record__c WHERE Contract__r.FTE_Tracker__c = 'Yes' AND Year__c >=: yearMin AND Year__c <=: yearMax ORDER BY Year__c DESC]) { + if (templatesMapping.containsKey(emplTemplate.Employee__c)) { + templatesMapping.get(emplTemplate.Employee__c).add(emplTemplate); + } else { + templatesMapping.put(emplTemplate.Employee__c, new List { emplTemplate }); + } + fteContracts.add(emplTemplate.Contract__c); + toUpdate.add(emplTemplate); + } + + for (Time_Card__c tc : timeCards) { + if (templatesMapping.containsKey(tc.Employee__c)) { // employee has templates + Boolean updated = false; + FTE_Data_Record__c templateTmp = null; + List templates = templatesMapping.get(tc.Employee__c); + if (fteContracts.contains(tc.Client__c)) { // contract is templated fte + for (FTE_Data_Record__c template : templates) { + if (template.Contract__c == tc.Client__c && tc.Date__c.year() == template.Year__c) { + markMonthToUpdate(template, tc.Date__c.month()); + updated = true; + } else if (templateTmp == null && tc.Date__c.year() == template.Year__c) { + templateTmp = template; + } + } + } else { + templateTmp = templates.get(0); + } + + if (updated == false && templateTmp != null) { // we want mark month when unassigned time was logged, we want trigger template for allocator + markMonthToUpdate(templateTmp, tc.Date__c.month()); + } + } + } + + if (toUpdate.size() > 0) { + update toUpdate; + } + } + + private static void markMonthToUpdate(FTE_Data_Record__c template, Integer month) { + Sobject sobj = (SObject) template; + sobj.put(FTETrackerHelper.getFieldUpdatedName(month), true); + } +} \ No newline at end of file diff --git a/src/classes/FTETriggerHelper.cls-meta.xml b/src/classes/FTETriggerHelper.cls-meta.xml new file mode 100644 index 00000000..cbddff8c --- /dev/null +++ b/src/classes/FTETriggerHelper.cls-meta.xml @@ -0,0 +1,5 @@ + + + 38.0 + Active + diff --git a/src/classes/FTEUpdateTagsBatch.cls b/src/classes/FTEUpdateTagsBatch.cls index cbc68efe..924dc305 100644 --- a/src/classes/FTEUpdateTagsBatch.cls +++ b/src/classes/FTEUpdateTagsBatch.cls @@ -51,7 +51,7 @@ public without sharing class FTEUpdateTagsBatch implements Database.Batchable= 0) { // we don't want add (-1) just skip it if employee data was duplicated in uploaded CSV + if (this.timeTable[month - 1] == -1) { + value += 1; + } this.timeTable[month - 1] = this.timeTable[month - 1] + value; } } @@ -48,11 +51,25 @@ public class FTEUploadData { return this.fteYear; } - public FTE_Data_Record__c buildDBRec() { - return new FTE_Data_Record__c(Month_1__c = this.timeTable[0], Month_2__c = this.timeTable[1], Month_3__c = this.timeTable[2], - Month_4__c = this.timeTable[3], Month_5__c = this.timeTable[4], Month_6__c = this.timeTable[5], - Month_7__c = this.timeTable[6],Month_8__c = this.timeTable[7], Month_9__c = this.timeTable[8], - Month_10__c = this.timeTable[9], Month_11__c = this.timeTable[10], Month_12__c = this.timeTable[11], - Year__c = this.fteYear, Contract__c = this.contractId, Employee__c = this.employeeId, Line_Number__c = 1); + public FTE_Data_Record__c mergeData(FTE_Data_Record__c rec) { + if (rec == null) { + rec = new FTE_Data_Record__c(Year__c = this.fteYear, Contract__c = this.contractId, Employee__c = this.employeeId); + } + + SObject recSObj = (SObject) rec; + for (Integer month = 1; month <= 12; month++) { + Decimal oldValue = null; + if (rec.Id != null) { + oldValue = (Decimal) recSObj.get(FTETrackerHelper.getFieldName(month)); + } + System.debug('Month merge : old : ' + oldValue + ' new ' + this.timeTable[month - 1]); + if (this.timeTable[month - 1] > -2 && ((oldValue == null && this.timeTable[month - 1] != -1) || (oldValue != null && oldValue != this.timeTable[month - 1]))) { // -2 means that nothing was uploaded for that month + System.debug('Month merge set true'); + recSObj.put(FTETrackerHelper.getFieldName(month), this.timeTable[month - 1]); + recSObj.put(FTETrackerHelper.getFieldUpdatedName(month), true); + } + } + + return (FTE_Data_Record__c) recSObj; } } \ No newline at end of file diff --git a/src/classes/TimeCardCaseUpdateBatch.cls b/src/classes/TimeCardCaseUpdateBatch.cls index 993cd6d0..56fa48bb 100644 --- a/src/classes/TimeCardCaseUpdateBatch.cls +++ b/src/classes/TimeCardCaseUpdateBatch.cls @@ -36,9 +36,10 @@ public class TimeCardCaseUpdateBatch implements Database.Batchable, String dbCaseTitle = fbCase.caseId + ': ' + fbCase.title; Integer clientFBId = Integer.valueOf(fbCase.client); - List timeCardsToUpdate = [SELECT Id, Area__c, Case__c, Project__c, Client__c FROM Time_Card__c WHERE Case_Number__c =: fbCase.caseId AND FTE_only__c = false - AND (Area__c !=: fbCase.area OR Case__c !=: dbCaseTitle OR Project__c !=: fbCase.project - OR Client__r.FB_Id__c !=: clientFBId)]; + List timeCardsToUpdate = [SELECT Id, Area__c, Case__c, Project__c, Client__c + FROM Time_Card__c WHERE Case_Number__c =: fbCase.caseId AND FTE_only__c = false + AND (Area__c !=: fbCase.area OR Case__c !=: dbCaseTitle OR Project__c !=: fbCase.project OR Client__r.FB_Id__c !=: clientFBId) + ORDER BY Employee__c]; List contracts = [SELECT Id, Name FROM DContract__c WHERE FB_Id__c =: clientFBId]; diff --git a/src/classes/TimeCardTriggerController.cls b/src/classes/TimeCardTriggerController.cls index 1fdd0f74..5cf0ade5 100644 --- a/src/classes/TimeCardTriggerController.cls +++ b/src/classes/TimeCardTriggerController.cls @@ -28,6 +28,8 @@ public class TimeCardTriggerController { if (tcToUpdate.size() > 0) { update tcToUpdate; } + + FTETriggerHelper.processTemplates(newTimeCards); } public static void handleAfterUpdate(List updatedTimeCards, Map beforeUpdateTimeCards) { @@ -37,6 +39,7 @@ public class TimeCardTriggerController { List tags = new List(); List timeCardsToUpdate = new List(); + List fteTimeCards = new List(); Set contractsIds = new Set(); for (Time_Card__c tc : updatedTimeCards) { @@ -46,6 +49,8 @@ public class TimeCardTriggerController { contractsIds.add(oldTC.Client__c); contractsIds.add(tc.Client__c); + fteTimeCards.add(tc); + Time_Card__c tcToUpdate = null; if (tc.FTE_Contract__c != null) { @@ -67,7 +72,7 @@ public class TimeCardTriggerController { if (tags.size() > 0) { insert tags; } - if (timeCardsToUpdate.size() > 0) { + if (timeCardsToUpdate.size() > 0) { // new contract we must recalculate time card billing rate Map billingRateMap = new Map(); TimeCardCalculatorHelper timeCardHelper = new TimeCardCalculatorHelper(); for (DContract__c client : [SELECT Id, Project_Billing_Rate__c FROM DContract__c WHERE Id IN: contractsIds]) { @@ -85,6 +90,10 @@ public class TimeCardTriggerController { if (contractsIds.size() > 0) { TimeCardCalculatorUtils.markContractsToRefresh(contractsIds); } + + if (fteTimeCards.size() > 0) { + FTETriggerHelper.processTemplates(fteTimeCards); + } } public static void handleAfterDelete(List deletedTimeCards) { @@ -105,5 +114,7 @@ public class TimeCardTriggerController { insert tags; } TimeCardCalculatorUtils.markContractsToRefresh(contractsIds); + + FTETriggerHelper.processTemplates(deletedTimeCards); } } \ No newline at end of file diff --git a/src/pages/FTE_CSV_Upload.page b/src/pages/FTE_CSV_Upload.page index 07ff1121..56eade50 100644 --- a/src/pages/FTE_CSV_Upload.page +++ b/src/pages/FTE_CSV_Upload.page @@ -5,22 +5,21 @@ margin-bottom: 10px; color: black; } - .leftMargin { - margin-left: 10px; - } + + Choose file from your computer and use "Parse and Upload CSV file" to process your data. + +



-
-
+

-
@@ -31,7 +30,6 @@ FTE Tracker is currently calculating time, {!workCardJobStatus.jobName} : {!workCardJobStatus.jobItemsProcessed}/{!workCardJobStatus.totalJobItems} -
@@ -43,42 +41,8 @@ - - - -
- - - - - - - - - - - - -
Page: - - -    - - - - Records per page:   - - - - - - -
- -
-
- - +
+ diff --git a/src/pages/FTE_Employee_List_View.page b/src/pages/FTE_Employee_List_View.page index dc5d485d..ab920bb0 100644 --- a/src/pages/FTE_Employee_List_View.page +++ b/src/pages/FTE_Employee_List_View.page @@ -3,11 +3,23 @@ .fteProjectCell { cursor: pointer; } + .fteCell { + cursor: pointer; + } + .highlight td { + background: #5fa6d4 !important; + } .fteProjectCell:hover { - background: #1797C0 !important; + background: #5fa6d4 !important; } .overbilled { - color: red !important; + color: #e50000 !important; + } + .future { + background: #87bcdf; + } + .blocked { + background: #ccdae5; } .fteTable { margin-top: 10px; @@ -21,6 +33,22 @@ margin-right: 10px; height: 22px !important; } + .tLegend { + display: inline-block; + } + .tLegendElement { + display: flex; + margin-bottom: 5px; + } + .tLegendColor { + width: 90px; + height: 19px; + padding: 0px; + margin: 0px; + } + .boldText { + font-weight: bold; + } @@ -33,7 +61,7 @@ FTE Tracker is currently calculating Empolyee Work Cards {!workCardJobStatus.jobItemsProcessed}/{!workCardJobStatus.totalJobItems} - +
@@ -64,27 +92,27 @@ - - + +

- - - - - - - - - - - - - + + + + + + + + + + + + + @@ -93,10 +121,10 @@ Page: - +    - + @@ -105,20 +133,26 @@ - + - - + + +
+
+
Table legend :
+
  - future time allocation
+
  - blocked time
+
diff --git a/src/pages/FTE_Employee_View.page b/src/pages/FTE_Employee_View.page index b9c9ce15..c1b9a34c 100644 --- a/src/pages/FTE_Employee_View.page +++ b/src/pages/FTE_Employee_View.page @@ -4,16 +4,22 @@ cursor: pointer; } .fteCell:hover { - background: #1797C0 !important; + background: #5fa6d4 !important; } .fteProjectCell { cursor: pointer; } .fteProjectCell:hover { - background: #1797C0 !important; + background: #5fa6d4 !important; } .overbilled { - color: red !important; + color: #e50000 !important; + } + .future { + background: #87bcdf !important; + } + .blocked { + background: #ccdae5 !important; } .topTotal { border-top-width: 3px !important; @@ -33,6 +39,19 @@ padding-left:5px; margin-top: 10px; } + .tLegend { + display: inline-block; + } + .tLegendElement { + display: flex; + margin-bottom: 5px; + } + .tLegendColor { + width: 90px; + height: 19px; + padding: 0px; + margin: 0px; + } .boldText { font-weight: bold; } @@ -43,6 +62,7 @@ line-height: 1.5em; padding: 10px 5px 30px 5px !important; min-width: 300px; + max-height: 400px; } .hoursModal label, input, span, h1, select { display:block; @@ -79,24 +99,64 @@ - + - + + + + + + FTE Tracker is currently calculating Empolyee Work Cards {!workCardJobStatus.jobItemsProcessed}/{!workCardJobStatus.totalJobItems} +
+
+ - - -
-
- -
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - - - - - -
-
- - - - - - - - - - - -
-
- - - - - - - - - - - - - -
-
- - - - - -
-
- - + + + +
+
+ +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +     + +
+
+
Table legend :
+
  - future time allocation
+
  - blocked time
- -
- - - - - - - - -     + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/pages/FTE_Individual_Project_View.page b/src/pages/FTE_Individual_Project_View.page index dac56250..048f3caa 100644 --- a/src/pages/FTE_Individual_Project_View.page +++ b/src/pages/FTE_Individual_Project_View.page @@ -1,11 +1,5 @@ @@ -59,18 +75,18 @@ - - - - - - - - - - - - + + + + + + + + + + + + @@ -79,9 +95,18 @@ + + +
+
+
Table legend :
+
  - future time allocation
+
  - blocked time
+
+
diff --git a/src/pages/FTE_Project_List_View.page b/src/pages/FTE_Project_List_View.page index 8b0a21a9..0783f51b 100644 --- a/src/pages/FTE_Project_List_View.page +++ b/src/pages/FTE_Project_List_View.page @@ -4,7 +4,7 @@ cursor: pointer; } .fteProjectCell:hover { - background: #1797C0 !important; + background: #5fa6d4 !important; } .overbilled { color: red !important; diff --git a/src/pages/FTE_Time_Card_View.page b/src/pages/FTE_Time_Card_View.page index a789bfbc..c0529793 100644 --- a/src/pages/FTE_Time_Card_View.page +++ b/src/pages/FTE_Time_Card_View.page @@ -84,7 +84,7 @@ -    Download CSV +    Download CSV diff --git a/src/triggers/DContractFTETrackerTrigger.trigger b/src/triggers/DContractFTETrackerTrigger.trigger index f6d9da87..fa9bbd92 100644 --- a/src/triggers/DContractFTETrackerTrigger.trigger +++ b/src/triggers/DContractFTETrackerTrigger.trigger @@ -1,35 +1,7 @@ trigger DContractFTETrackerTrigger on DContract__c (before update, after update) { if (Trigger.isBefore && FTETrackerHelper.loadWorkCardJobStatus().isRunning) { - for (DContract__c upsertedContract : Trigger.new) { - DContract__c oldValue = Trigger.oldMap.get(upsertedContract.Id); - if ((oldValue == null || oldValue.FTE_Tracker__c != upsertedContract.FTE_Tracker__c)) { - upsertedContract.addError('FTE Tracker is currently calculating Empolyee Work Cards, try update FTE Tracker field later.'); - } - } + DContractTriggerController.handleBeforeUpdate(Trigger.new, Trigger.oldMap); } else if (Trigger.isAfter) { - Set contracts = new Set(); - List contractsTOUpdate = new List(); - Boolean fteJob = FTETrackerHelper.loadWorkCardJobStatus().isRunning; - - for (DContract__c upsertedContract : Trigger.new) { - DContract__c oldValue = Trigger.oldMap.get(upsertedContract.Id); - if ((oldValue == null || oldValue.FTE_Tracker__c != upsertedContract.FTE_Tracker__c) && (upsertedContract.Skip_FTE_Tracker_Trigger__c == false)) { - contracts.add(upsertedContract.Id); - } else if (upsertedContract.Skip_FTE_Tracker_Trigger__c == true) { // we want skip this batch job FTE Contract is uploaded by CSV File, we want have full controll and run moving hours batch job after Generating work cards - contractsTOUpdate.add(new DContract__c(Id = upsertedContract.Id, Skip_FTE_Tracker_Trigger__c = false)); - } - } - - if (contractsTOUpdate.size() > 0) { - update contractsTOUpdate; - } - - if (contracts.size() > 0) { - if (!Test.isRunningTest()) { - Database.executeBatch(new FTEGenerateEmployeesWorkCardBatch(), 1); - } else { - Database.executeBatch(new FTEGenerateEmployeesWorkCardBatch()); - } - } + DContractTriggerController.handleAfterUpdate(Trigger.new, Trigger.oldMap); } } \ No newline at end of file