001 /* 002 * Copyright 2011 The Kuali Foundation. 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 package org.kuali.kfs.module.ld.document.service.impl; 017 018 import java.util.ArrayList; 019 import java.util.HashMap; 020 import java.util.List; 021 import java.util.Map; 022 023 import org.apache.commons.lang.StringUtils; 024 import org.kuali.kfs.coa.businessobject.A21SubAccount; 025 import org.kuali.kfs.integration.ec.EffortCertificationModuleService; 026 import org.kuali.kfs.integration.ec.EffortCertificationReport; 027 import org.kuali.kfs.module.ld.LaborKeyConstants; 028 import org.kuali.kfs.module.ld.businessobject.ExpenseTransferAccountingLine; 029 import org.kuali.kfs.module.ld.businessobject.ExpenseTransferSourceAccountingLine; 030 import org.kuali.kfs.module.ld.document.SalaryExpenseTransferDocument; 031 import org.kuali.kfs.module.ld.document.service.SalaryTransferPeriodValidationService; 032 import org.kuali.kfs.sys.KFSConstants; 033 import org.kuali.kfs.sys.KFSPropertyConstants; 034 import org.kuali.kfs.sys.context.SpringContext; 035 import org.kuali.rice.kim.bo.Person; 036 import org.kuali.rice.kim.service.PersonService; 037 import org.kuali.rice.kns.bo.Note; 038 import org.kuali.rice.kns.service.DocumentService; 039 import org.kuali.rice.kns.service.KualiConfigurationService; 040 import org.kuali.rice.kns.service.NoteService; 041 import org.kuali.rice.kns.util.GlobalVariables; 042 import org.kuali.rice.kns.util.KualiDecimal; 043 import org.kuali.rice.kns.util.ObjectUtils; 044 import org.springframework.transaction.annotation.Transactional; 045 046 /** 047 * @see org.kuali.kfs.module.ld.document.service.SalaryTransferPeriodValidationService 048 */ 049 @Transactional 050 public class SalaryTransferPeriodValidationServiceImpl implements SalaryTransferPeriodValidationService { 051 private EffortCertificationModuleService effortCertificationService; 052 private DocumentService documentService; 053 private NoteService noteService; 054 private KualiConfigurationService kualiConfigurationService; 055 private PersonService<Person> personService; 056 057 /** 058 * @see org.kuali.kfs.module.ld.document.service.SalaryTransferPeriodValidationService#validateTransfers(org.kuali.kfs.module.ld.document.SalaryExpenseTransferDocument) 059 */ 060 public boolean validateTransfers(SalaryExpenseTransferDocument document) { 061 List<ExpenseTransferAccountingLine> transferLinesInOpenPeriod = new ArrayList<ExpenseTransferAccountingLine>(); 062 063 // check for closed or open reporting period(s) ... closed periods result in error, open periods require more validation 064 List<ExpenseTransferAccountingLine> allLines = new ArrayList<ExpenseTransferAccountingLine>(document.getSourceAccountingLines()); 065 allLines.addAll(document.getTargetAccountingLines()); 066 for (ExpenseTransferAccountingLine transferLine : allLines) { 067 // check we have enough data for validation, if not business rules will report error 068 if (!containsNecessaryData(transferLine)) { 069 continue; 070 } 071 072 // if closed report found then return error 073 EffortCertificationReport closedReport = getClosedReportingPeriod(transferLine); 074 if (closedReport != null) { 075 putError(LaborKeyConstants.ERROR_EFFORT_CLOSED_REPORT_PERIOD, transferLine, closedReport); 076 return false; 077 } 078 079 // if open report(s) found then add transfer line to list for further validation 080 EffortCertificationReport openReport = getOpenReportingPeriod(transferLine); 081 if (openReport != null) { 082 transferLinesInOpenPeriod.add(transferLine); 083 } 084 } 085 086 // verify transfers will not affect the open reporting period 087 Map<String, KualiDecimal> accountPeriodTransfer = new HashMap<String, KualiDecimal>(); 088 EffortCertificationReport emplidReport = null; 089 for (ExpenseTransferAccountingLine transferLine : transferLinesInOpenPeriod) { 090 emplidReport = isEmployeeWithOpenCertification(transferLine, document.getEmplid()); 091 if (emplidReport != null) { 092 // if employee has a report, transfer lines cannot use cost share sub-accounts 093 if (isCostShareSubAccount(transferLine)) { 094 putError(LaborKeyConstants.ERROR_EFFORT_OPEN_PERIOD_COST_SHARE, transferLine, emplidReport); 095 return false; 096 } 097 098 // add line amount for validation later 099 addAccountTransferAmount(accountPeriodTransfer, transferLine, emplidReport); 100 } 101 else { 102 // if employee does not have a report, transfer lines cannot use CG accounts 103 if (transferLine.getAccount().isForContractsAndGrants()) { 104 EffortCertificationReport openReport = getOpenReportingPeriod(transferLine); 105 putError(LaborKeyConstants.ERROR_EFFORT_OPEN_PERIOD_CG_ACCOUNT, transferLine, openReport); 106 return false; 107 } 108 } 109 } 110 111 // verify balance is same for accounts in transfer map 112 for (String transferKey : accountPeriodTransfer.keySet()) { 113 KualiDecimal transfer = accountPeriodTransfer.get(transferKey); 114 if (transfer.isNonZero()) { 115 String[] keyFields = StringUtils.split(transferKey, ","); 116 GlobalVariables.getMessageMap().putError(KFSPropertyConstants.SOURCE_ACCOUNTING_LINES, LaborKeyConstants.ERROR_EFFORT_OPEN_PERIOD_ACCOUNTS_NOT_BALANCED, new String[] { keyFields[4], keyFields[0], keyFields[1] }); 117 return false; 118 } 119 } 120 121 return true; 122 } 123 124 /** 125 * @see org.kuali.kfs.module.ld.document.service.SalaryTransferPeriodValidationService#disapproveSalaryExpenseDocument(org.kuali.kfs.module.ld.document.SalaryExpenseTransferDocument) 126 */ 127 public void disapproveSalaryExpenseDocument(SalaryExpenseTransferDocument document) throws Exception { 128 // create note explaining why the document was disapproved 129 Note cancelNote = noteService.createNote(new Note(), document.getDocumentHeader()); 130 cancelNote.setNoteText(kualiConfigurationService.getPropertyString(LaborKeyConstants.EFFORT_AUTO_DISAPPROVE_MESSAGE)); 131 132 Person systemUser = getPersonService().getPersonByPrincipalName(KFSConstants.SYSTEM_USER); 133 cancelNote.setAuthorUniversalIdentifier(systemUser.getPrincipalId()); 134 noteService.save(cancelNote); 135 document.addNote(cancelNote); 136 137 documentService.disapproveDocument(document, "disapproved - failed effort certification checks"); 138 } 139 140 /** 141 * Checks list of report definitions for a closed period. 142 * 143 * @param transferLine - transfer line to find report definition for 144 * @return closed report or null if one is not found 145 */ 146 protected EffortCertificationReport getClosedReportingPeriod(ExpenseTransferAccountingLine transferLine) { 147 List<EffortCertificationReport> effortReports = getEffortReportDefinitionsForLine(transferLine); 148 149 for (EffortCertificationReport report : effortReports) { 150 if (KFSConstants.PeriodStatusCodes.CLOSED.equals(report.getEffortCertificationReportPeriodStatusCode())) { 151 return report; 152 } 153 } 154 155 return null; 156 } 157 158 /** 159 * Checks list of report definitions for a open period. 160 * 161 * @param transferLine - transfer line to find report definition for 162 * @return open report or null if one is not found 163 */ 164 protected EffortCertificationReport getOpenReportingPeriod(ExpenseTransferAccountingLine transferLine) { 165 List<EffortCertificationReport> effortReports = getEffortReportDefinitionsForLine(transferLine); 166 167 for (EffortCertificationReport report : effortReports) { 168 if (KFSConstants.PeriodStatusCodes.OPEN.equals(report.getEffortCertificationReportPeriodStatusCode())) { 169 return report; 170 } 171 } 172 173 return null; 174 } 175 176 /** 177 * Returns the open report periods from the given list of report definitions. 178 * 179 * @param effortReports - list of report definitions that are either open or closed 180 * @return open effort report definitions 181 */ 182 protected List<EffortCertificationReport> getOpenReportDefinitions(List<EffortCertificationReport> effortReports) { 183 List<EffortCertificationReport> openReports = new ArrayList<EffortCertificationReport>(); 184 185 for (EffortCertificationReport report : effortReports) { 186 if (KFSConstants.PeriodStatusCodes.OPEN.equals(report.getEffortCertificationReportPeriodStatusCode())) { 187 openReports.add(report); 188 } 189 } 190 191 return openReports; 192 } 193 194 /** 195 * Checks the sub account type code against the values defined for cost share. 196 * 197 * @param transferLine - line with sub account to check 198 * @return true if sub account is cost share, false otherwise 199 */ 200 protected boolean isCostShareSubAccount(ExpenseTransferAccountingLine transferLine) { 201 boolean isCostShare = false; 202 203 if (ObjectUtils.isNotNull(transferLine.getSubAccount()) && ObjectUtils.isNotNull(transferLine.getSubAccount().getA21SubAccount())) { 204 A21SubAccount a21SubAccount = transferLine.getSubAccount().getA21SubAccount(); 205 String subAccountTypeCode = a21SubAccount.getSubAccountTypeCode(); 206 207 List<String> costShareSubAccountTypeCodes = effortCertificationService.getCostShareSubAccountTypeCodes(); 208 if (costShareSubAccountTypeCodes.contains(subAccountTypeCode)) { 209 isCostShare = true; 210 } 211 } 212 213 return isCostShare; 214 } 215 216 /** 217 * Finds all open effort reports for the given transfer line, then checks if the given emplid has a certification for one of 218 * those open reports. 219 * 220 * @param transferLine - line to find open reports for 221 * @param emplid - emplid to check for certification 222 * @return report which emplid has certification, or null 223 */ 224 protected EffortCertificationReport isEmployeeWithOpenCertification(ExpenseTransferAccountingLine transferLine, String emplid) { 225 List<EffortCertificationReport> effortReports = getEffortReportDefinitionsForLine(transferLine); 226 List<EffortCertificationReport> openEffortReports = getOpenReportDefinitions(effortReports); 227 228 return effortCertificationService.isEmployeeWithOpenCertification(openEffortReports, emplid); 229 } 230 231 /** 232 * Adds the line amount to the given map that contains the total transfer amount for the account and period. 233 * 234 * @param accountPeriodTransfer - map holding the total transfers 235 * @param effortReport - open report for transfer line 236 * @param transferLine - line with amount to add 237 */ 238 protected void addAccountTransferAmount(Map<String, KualiDecimal> accountPeriodTransfer, ExpenseTransferAccountingLine transferLine, EffortCertificationReport effortReport) { 239 String transferKey = StringUtils.join(new Object[] { transferLine.getPayrollEndDateFiscalYear(), transferLine.getPayrollEndDateFiscalPeriodCode(), transferLine.getChartOfAccountsCode(), transferLine.getAccountNumber(), effortReport.getUniversityFiscalYear()+ "-" + effortReport.getEffortCertificationReportNumber() }, ","); 240 241 KualiDecimal transferAmount = transferLine.getAmount().abs(); 242 if (transferLine instanceof ExpenseTransferSourceAccountingLine) { 243 transferAmount = transferAmount.negated(); 244 } 245 246 if (accountPeriodTransfer.containsKey(transferKey)) { 247 transferAmount = transferAmount.add(accountPeriodTransfer.get(transferKey)); 248 } 249 250 accountPeriodTransfer.put(transferKey, transferAmount); 251 } 252 253 /** 254 * Gets open or closed report definitions for line pay period and pay type. 255 * 256 * @param transferLine - line to pull pay period and type from 257 * @return - open or closed effort reports for period and type 258 */ 259 protected List<EffortCertificationReport> getEffortReportDefinitionsForLine(ExpenseTransferAccountingLine transferLine) { 260 Integer payFiscalYear = transferLine.getPayrollEndDateFiscalYear(); 261 String payFiscalPeriodCode = transferLine.getPayrollEndDateFiscalPeriodCode(); 262 String positionObjectGroupCode = transferLine.getLaborObject().getPositionObjectGroupCode(); 263 264 return effortCertificationService.findReportDefinitionsForPeriod(payFiscalYear, payFiscalPeriodCode, positionObjectGroupCode); 265 } 266 267 /** 268 * Verfies the given tranfer line contains the necessary data for performing the effort validations. 269 * 270 * @param transferLine - line to check 271 */ 272 protected boolean containsNecessaryData(ExpenseTransferAccountingLine transferLine) { 273 //KFSMI-798 - refreshNonUpdatableReferences() used instead of refresh(), 274 //Both ExpenseTransferSourceAccountingLine and ExpenseTransferTargetAccountingLine do not have any updatable references 275 transferLine.refreshNonUpdateableReferences(); 276 277 if (ObjectUtils.isNull(transferLine.getAccount()) || ObjectUtils.isNull(transferLine.getLaborObject()) || ObjectUtils.isNull(transferLine.getAmount())) { 278 return false; 279 } 280 281 if (transferLine.getPayrollEndDateFiscalYear() == null || transferLine.getPayrollEndDateFiscalPeriodCode() == null) { 282 return false; 283 } 284 285 return true; 286 } 287 288 /** 289 * Determines whether the error should be associated with the source or target lines, and builds up parameters for error 290 * message. 291 * 292 * @param errorKey - key for the error message 293 * @param transferLine - transfer line which had error 294 * @param report - report which conflicted with line 295 */ 296 protected void putError(String errorKey, ExpenseTransferAccountingLine transferLine, EffortCertificationReport report) { 297 String errorLines = KFSPropertyConstants.TARGET_ACCOUNTING_LINES; 298 if (transferLine instanceof ExpenseTransferSourceAccountingLine) { 299 errorLines = KFSPropertyConstants.SOURCE_ACCOUNTING_LINES; 300 } 301 302 String[] errorParameters = new String[3]; 303 errorParameters[0] = report.getUniversityFiscalYear() + "-" + report.getEffortCertificationReportNumber(); 304 errorParameters[1] = transferLine.getPayrollEndDateFiscalYear().toString(); 305 errorParameters[2] = transferLine.getPayrollEndDateFiscalPeriodCode(); 306 307 GlobalVariables.getMessageMap().putError(errorLines, errorKey, errorParameters); 308 } 309 310 /** 311 * Sets the documentService attribute value. 312 * 313 * @param documentService The documentService to set. 314 */ 315 public void setDocumentService(DocumentService documentService) { 316 this.documentService = documentService; 317 } 318 319 /** 320 * Sets the effortCertificationService attribute value. 321 * 322 * @param effortCertificationService The effortCertificationService to set. 323 */ 324 public void setEffortCertificationService(EffortCertificationModuleService effortCertificationService) { 325 this.effortCertificationService = effortCertificationService; 326 } 327 328 /** 329 * Sets the noteService attribute value. 330 * 331 * @param noteService The noteService to set. 332 */ 333 public void setNoteService(NoteService noteService) { 334 this.noteService = noteService; 335 } 336 337 /** 338 * Sets the kualiConfigurationService attribute value. 339 * 340 * @param kualiConfigurationService The kualiConfigurationService to set. 341 */ 342 public void setKualiConfigurationService(KualiConfigurationService kualiConfigurationService) { 343 this.kualiConfigurationService = kualiConfigurationService; 344 } 345 346 /** 347 * @return Returns the personService. 348 */ 349 protected PersonService<Person> getPersonService() { 350 if(personService==null) 351 personService = SpringContext.getBean(PersonService.class); 352 return personService; 353 } 354 355 } 356