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