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