/* * The Kuali Financial System, a comprehensive financial management system for higher education. * * Copyright 2005-2014 The Kuali Foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.kuali.kfs.module.ld.document.service.impl; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.lang.StringUtils; import org.kuali.kfs.coa.businessobject.A21SubAccount; import org.kuali.kfs.integration.ec.EffortCertificationModuleService; import org.kuali.kfs.integration.ec.EffortCertificationReport; import org.kuali.kfs.module.ld.LaborKeyConstants; import org.kuali.kfs.module.ld.businessobject.ExpenseTransferAccountingLine; import org.kuali.kfs.module.ld.businessobject.ExpenseTransferSourceAccountingLine; import org.kuali.kfs.module.ld.document.SalaryExpenseTransferDocument; import org.kuali.kfs.module.ld.document.service.SalaryTransferPeriodValidationService; import org.kuali.kfs.sys.KFSConstants; import org.kuali.kfs.sys.KFSPropertyConstants; import org.kuali.rice.core.api.config.property.ConfigurationService; import org.kuali.rice.core.api.util.type.KualiDecimal; import org.kuali.rice.kim.api.identity.principal.Principal; import org.kuali.rice.kim.api.services.KimApiServiceLocator; import org.kuali.rice.krad.bo.Note; import org.kuali.rice.krad.service.DocumentService; import org.kuali.rice.krad.service.NoteService; import org.kuali.rice.krad.util.GlobalVariables; import org.kuali.rice.krad.util.ObjectUtils; import org.springframework.transaction.annotation.Transactional; /** * @see org.kuali.kfs.module.ld.document.service.SalaryTransferPeriodValidationService */ @Transactional public class SalaryTransferPeriodValidationServiceImpl implements SalaryTransferPeriodValidationService { private EffortCertificationModuleService effortCertificationService; private DocumentService documentService; private NoteService noteService; private ConfigurationService kualiConfigurationService; /** * @see org.kuali.kfs.module.ld.document.service.SalaryTransferPeriodValidationService#validateTransfers(org.kuali.kfs.module.ld.document.SalaryExpenseTransferDocument) */ @Override public boolean validateTransfers(SalaryExpenseTransferDocument document) { List<ExpenseTransferAccountingLine> transferLinesInOpenPeriod = new ArrayList<ExpenseTransferAccountingLine>(); // check for closed or open reporting period(s) ... closed periods result in error, open periods require more validation List<ExpenseTransferAccountingLine> allLines = new ArrayList<ExpenseTransferAccountingLine>(document.getSourceAccountingLines()); allLines.addAll(document.getTargetAccountingLines()); for (ExpenseTransferAccountingLine transferLine : allLines) { // check we have enough data for validation, if not business rules will report error if (!containsNecessaryData(transferLine)) { continue; } // if closed report found then return error EffortCertificationReport closedReport = getClosedReportingPeriod(transferLine); if (closedReport != null) { putError(LaborKeyConstants.ERROR_EFFORT_CLOSED_REPORT_PERIOD, transferLine, closedReport); return false; } // if open report(s) found then add transfer line to list for further validation EffortCertificationReport openReport = getOpenReportingPeriod(transferLine); if (openReport != null) { transferLinesInOpenPeriod.add(transferLine); } } // verify transfers will not affect the open reporting period Map<String, KualiDecimal> accountPeriodTransfer = new HashMap<String, KualiDecimal>(); EffortCertificationReport emplidReport = null; for (ExpenseTransferAccountingLine transferLine : transferLinesInOpenPeriod) { emplidReport = isEmployeeWithOpenCertification(transferLine, document.getEmplid()); if (emplidReport != null) { // if employee has a report, transfer lines cannot use cost share sub-accounts if (isCostShareSubAccount(transferLine)) { putError(LaborKeyConstants.ERROR_EFFORT_OPEN_PERIOD_COST_SHARE, transferLine, emplidReport); return false; } // add line amount for validation later addAccountTransferAmount(accountPeriodTransfer, transferLine, emplidReport); } else { // if employee does not have a report, transfer lines cannot use CG accounts if (transferLine.getAccount().isForContractsAndGrants()) { EffortCertificationReport openReport = getOpenReportingPeriod(transferLine); putError(LaborKeyConstants.ERROR_EFFORT_OPEN_PERIOD_CG_ACCOUNT, transferLine, openReport); return false; } } } // verify balance is same for accounts in transfer map for (String transferKey : accountPeriodTransfer.keySet()) { KualiDecimal transfer = accountPeriodTransfer.get(transferKey); if (transfer.isNonZero()) { String[] keyFields = StringUtils.split(transferKey, ","); GlobalVariables.getMessageMap().putError(KFSPropertyConstants.SOURCE_ACCOUNTING_LINES, LaborKeyConstants.ERROR_EFFORT_OPEN_PERIOD_ACCOUNTS_NOT_BALANCED, new String[] { keyFields[4], keyFields[0], keyFields[1] }); return false; } } return true; } /** * @see org.kuali.kfs.module.ld.document.service.SalaryTransferPeriodValidationService#disapproveSalaryExpenseDocument(org.kuali.kfs.module.ld.document.SalaryExpenseTransferDocument) */ @Override public void disapproveSalaryExpenseDocument(SalaryExpenseTransferDocument document) throws Exception { // create note explaining why the document was disapproved String message = kualiConfigurationService.getPropertyValueAsString(LaborKeyConstants.EFFORT_AUTO_DISAPPROVE_MESSAGE); Note cancelNote = documentService.createNoteFromDocument( document, message); Principal principal = KimApiServiceLocator.getIdentityService().getPrincipalByPrincipalName(KFSConstants.SYSTEM_USER); cancelNote.setAuthorUniversalIdentifier(principal.getPrincipalId()); noteService.save(cancelNote); document.addNote(cancelNote); documentService.disapproveDocument(document, "disapproved - failed effort certification checks"); } /** * Checks list of report definitions for a closed period. * * @param transferLine - transfer line to find report definition for * @return closed report or null if one is not found */ protected EffortCertificationReport getClosedReportingPeriod(ExpenseTransferAccountingLine transferLine) { List<EffortCertificationReport> effortReports = getEffortReportDefinitionsForLine(transferLine); for (EffortCertificationReport report : effortReports) { if (KFSConstants.PeriodStatusCodes.CLOSED.equals(report.getEffortCertificationReportPeriodStatusCode())) { return report; } } return null; } /** * Checks list of report definitions for a open period. * * @param transferLine - transfer line to find report definition for * @return open report or null if one is not found */ protected EffortCertificationReport getOpenReportingPeriod(ExpenseTransferAccountingLine transferLine) { List<EffortCertificationReport> effortReports = getEffortReportDefinitionsForLine(transferLine); for (EffortCertificationReport report : effortReports) { if (KFSConstants.PeriodStatusCodes.OPEN.equals(report.getEffortCertificationReportPeriodStatusCode())) { return report; } } return null; } /** * Returns the open report periods from the given list of report definitions. * * @param effortReports - list of report definitions that are either open or closed * @return open effort report definitions */ protected List<EffortCertificationReport> getOpenReportDefinitions(List<EffortCertificationReport> effortReports) { List<EffortCertificationReport> openReports = new ArrayList<EffortCertificationReport>(); for (EffortCertificationReport report : effortReports) { if (KFSConstants.PeriodStatusCodes.OPEN.equals(report.getEffortCertificationReportPeriodStatusCode())) { openReports.add(report); } } return openReports; } /** * Checks the sub account type code against the values defined for cost share. * * @param transferLine - line with sub account to check * @return true if sub account is cost share, false otherwise */ protected boolean isCostShareSubAccount(ExpenseTransferAccountingLine transferLine) { boolean isCostShare = false; if (ObjectUtils.isNotNull(transferLine.getSubAccount()) && ObjectUtils.isNotNull(transferLine.getSubAccount().getA21SubAccount())) { A21SubAccount a21SubAccount = transferLine.getSubAccount().getA21SubAccount(); String subAccountTypeCode = a21SubAccount.getSubAccountTypeCode(); List<String> costShareSubAccountTypeCodes = effortCertificationService.getCostShareSubAccountTypeCodes(); if (costShareSubAccountTypeCodes.contains(subAccountTypeCode)) { isCostShare = true; } } return isCostShare; } /** * Finds all open effort reports for the given transfer line, then checks if the given emplid has a certification for one of * those open reports. * * @param transferLine - line to find open reports for * @param emplid - emplid to check for certification * @return report which emplid has certification, or null */ protected EffortCertificationReport isEmployeeWithOpenCertification(ExpenseTransferAccountingLine transferLine, String emplid) { List<EffortCertificationReport> effortReports = getEffortReportDefinitionsForLine(transferLine); List<EffortCertificationReport> openEffortReports = getOpenReportDefinitions(effortReports); return effortCertificationService.isEmployeeWithOpenCertification(openEffortReports, emplid); } /** * Adds the line amount to the given map that contains the total transfer amount for the account and period. * * @param accountPeriodTransfer - map holding the total transfers * @param effortReport - open report for transfer line * @param transferLine - line with amount to add */ protected void addAccountTransferAmount(Map<String, KualiDecimal> accountPeriodTransfer, ExpenseTransferAccountingLine transferLine, EffortCertificationReport effortReport) { String transferKey = StringUtils.join(new Object[] { transferLine.getPayrollEndDateFiscalYear(), transferLine.getPayrollEndDateFiscalPeriodCode(), transferLine.getChartOfAccountsCode(), transferLine.getAccountNumber(), effortReport.getUniversityFiscalYear()+ "-" + effortReport.getEffortCertificationReportNumber() }, ","); KualiDecimal transferAmount = transferLine.getAmount().abs(); if (transferLine instanceof ExpenseTransferSourceAccountingLine) { transferAmount = transferAmount.negated(); } if (accountPeriodTransfer.containsKey(transferKey)) { transferAmount = transferAmount.add(accountPeriodTransfer.get(transferKey)); } accountPeriodTransfer.put(transferKey, transferAmount); } /** * Gets open or closed report definitions for line pay period and pay type. * * @param transferLine - line to pull pay period and type from * @return - open or closed effort reports for period and type */ protected List<EffortCertificationReport> getEffortReportDefinitionsForLine(ExpenseTransferAccountingLine transferLine) { Integer payFiscalYear = transferLine.getPayrollEndDateFiscalYear(); String payFiscalPeriodCode = transferLine.getPayrollEndDateFiscalPeriodCode(); String positionObjectGroupCode = transferLine.getLaborObject().getPositionObjectGroupCode(); return effortCertificationService.findReportDefinitionsForPeriod(payFiscalYear, payFiscalPeriodCode, positionObjectGroupCode); } /** * Verfies the given tranfer line contains the necessary data for performing the effort validations. * * @param transferLine - line to check */ protected boolean containsNecessaryData(ExpenseTransferAccountingLine transferLine) { //KFSMI-798 - refreshNonUpdatableReferences() used instead of refresh(), //Both ExpenseTransferSourceAccountingLine and ExpenseTransferTargetAccountingLine do not have any updatable references transferLine.refreshNonUpdateableReferences(); if (ObjectUtils.isNull(transferLine.getAccount()) || ObjectUtils.isNull(transferLine.getLaborObject()) || ObjectUtils.isNull(transferLine.getAmount())) { return false; } if (transferLine.getPayrollEndDateFiscalYear() == null || transferLine.getPayrollEndDateFiscalPeriodCode() == null) { return false; } return true; } /** * Determines whether the error should be associated with the source or target lines, and builds up parameters for error * message. * * @param errorKey - key for the error message * @param transferLine - transfer line which had error * @param report - report which conflicted with line */ protected void putError(String errorKey, ExpenseTransferAccountingLine transferLine, EffortCertificationReport report) { String errorLines = KFSPropertyConstants.TARGET_ACCOUNTING_LINES; if (transferLine instanceof ExpenseTransferSourceAccountingLine) { errorLines = KFSPropertyConstants.SOURCE_ACCOUNTING_LINES; } String[] errorParameters = new String[3]; errorParameters[0] = report.getUniversityFiscalYear() + "-" + report.getEffortCertificationReportNumber(); errorParameters[1] = transferLine.getPayrollEndDateFiscalYear().toString(); errorParameters[2] = transferLine.getPayrollEndDateFiscalPeriodCode(); GlobalVariables.getMessageMap().putError(errorLines, errorKey, errorParameters); } /** * Sets the documentService attribute value. * * @param documentService The documentService to set. */ public void setDocumentService(DocumentService documentService) { this.documentService = documentService; } /** * Sets the effortCertificationService attribute value. * * @param effortCertificationService The effortCertificationService to set. */ public void setEffortCertificationService(EffortCertificationModuleService effortCertificationService) { this.effortCertificationService = effortCertificationService; } /** * Sets the noteService attribute value. * * @param noteService The noteService to set. */ public void setNoteService(NoteService noteService) { this.noteService = noteService; } /** * Sets the kualiConfigurationService attribute value. * * @param kualiConfigurationService The kualiConfigurationService to set. */ public void setConfigurationService(ConfigurationService kualiConfigurationService) { this.kualiConfigurationService = kualiConfigurationService; } }