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.ec.service.impl;
017    
018    import java.math.BigDecimal;
019    import java.util.ArrayList;
020    import java.util.Collection;
021    import java.util.HashMap;
022    import java.util.HashSet;
023    import java.util.List;
024    import java.util.Map;
025    import java.util.Set;
026    
027    import org.apache.commons.lang.StringUtils;
028    import org.kuali.kfs.coa.businessobject.Account;
029    import org.kuali.kfs.integration.cg.ContractsAndGrantsModuleService;
030    import org.kuali.kfs.integration.ld.LaborLedgerExpenseTransferAccountingLine;
031    import org.kuali.kfs.integration.ld.LaborLedgerExpenseTransferSourceAccountingLine;
032    import org.kuali.kfs.integration.ld.LaborLedgerExpenseTransferTargetAccountingLine;
033    import org.kuali.kfs.integration.ld.LaborModuleService;
034    import org.kuali.kfs.module.ec.EffortConstants;
035    import org.kuali.kfs.module.ec.EffortKeyConstants;
036    import org.kuali.kfs.module.ec.businessobject.EffortCertificationDetail;
037    import org.kuali.kfs.module.ec.businessobject.EffortCertificationDetailBuild;
038    import org.kuali.kfs.module.ec.businessobject.EffortCertificationDetailLineOverride;
039    import org.kuali.kfs.module.ec.businessobject.EffortCertificationDocumentBuild;
040    import org.kuali.kfs.module.ec.businessobject.EffortCertificationReportDefinition;
041    import org.kuali.kfs.module.ec.document.EffortCertificationDocument;
042    import org.kuali.kfs.module.ec.document.validation.impl.EffortCertificationDocumentRuleUtil;
043    import org.kuali.kfs.module.ec.service.EffortCertificationDocumentService;
044    import org.kuali.kfs.sys.KFSConstants;
045    import org.kuali.kfs.sys.KFSPropertyConstants;
046    import org.kuali.kfs.sys.MessageBuilder;
047    import org.kuali.kfs.sys.businessobject.AccountingLineOverride;
048    import org.kuali.kfs.sys.businessobject.FinancialSystemDocumentHeader;
049    import org.kuali.rice.kew.exception.WorkflowException;
050    import org.kuali.rice.kew.util.KEWConstants;
051    import org.kuali.rice.kim.bo.Person;
052    import org.kuali.rice.kns.UserSession;
053    import org.kuali.rice.kns.bo.AdHocRoutePerson;
054    import org.kuali.rice.kns.service.BusinessObjectService;
055    import org.kuali.rice.kns.service.DocumentService;
056    import org.kuali.rice.kns.service.KualiModuleService;
057    import org.kuali.rice.kns.util.GlobalVariables;
058    import org.kuali.rice.kns.util.KualiDecimal;
059    import org.kuali.rice.kns.util.spring.Logged;
060    import org.kuali.rice.kns.workflow.service.KualiWorkflowDocument;
061    import org.springframework.transaction.annotation.Transactional;
062    
063    /**
064     * To implement the services related to the effort certification document
065     */
066    @Transactional
067    public class EffortCertificationDocumentServiceImpl implements EffortCertificationDocumentService {
068        public static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(EffortCertificationDocumentServiceImpl.class);
069    
070        private LaborModuleService laborModuleService;
071        private KualiModuleService kualiModuleService;
072        private ContractsAndGrantsModuleService contractsAndGrantsModuleService;
073    
074        private DocumentService documentService;
075        private BusinessObjectService businessObjectService;
076    
077        /**
078         * @see org.kuali.kfs.module.ec.service.EffortCertificationDocumentService#processApprovedEffortCertificationDocument(org.kuali.kfs.module.ec.document.EffortCertificationDocument)
079         */
080        public void processApprovedEffortCertificationDocument(EffortCertificationDocument effortCertificationDocument) {
081            KualiWorkflowDocument workflowDocument = effortCertificationDocument.getDocumentHeader().getWorkflowDocument();
082    
083            if (workflowDocument.stateIsFinal()) {
084                GlobalVariables.setUserSession(new UserSession(KFSConstants.SYSTEM_USER));
085                this.generateSalaryExpenseTransferDocument(effortCertificationDocument);
086            }
087        }
088    
089        /**
090         * @see org.kuali.kfs.module.ec.service.EffortCertificationDocumentService#createAndRouteEffortCertificationDocument(org.kuali.kfs.module.ec.businessobject.EffortCertificationDocumentBuild)
091         */
092        @Logged
093        public boolean createAndRouteEffortCertificationDocument(EffortCertificationDocumentBuild effortCertificationDocumentBuild) {
094            try {
095                EffortCertificationDocument effortCertificationDocument = (EffortCertificationDocument) documentService.getNewDocument(EffortConstants.EffortDocumentTypes.EFFORT_CERTIFICATION_DOCUMENT);
096                this.populateEffortCertificationDocument(effortCertificationDocument, effortCertificationDocumentBuild);
097                documentService.routeDocument(effortCertificationDocument, KFSConstants.EMPTY_STRING, null);
098            }
099            catch (WorkflowException we) {
100                LOG.error(we);
101                throw new RuntimeException(we);
102            }
103    
104            return true;
105        }
106    
107        /**
108         * @see org.kuali.kfs.module.ec.service.EffortCertificationDocumentService#populateEffortCertificationDocument(org.kuali.kfs.module.ec.document.EffortCertificationDocument,
109         *      org.kuali.kfs.module.ec.businessobject.EffortCertificationDocumentBuild)
110         */
111        @Logged
112        public boolean populateEffortCertificationDocument(EffortCertificationDocument effortCertificationDocument, EffortCertificationDocumentBuild effortCertificationDocumentBuild) {
113            // populate the fields of the docuemnt
114            effortCertificationDocument.setUniversityFiscalYear(effortCertificationDocumentBuild.getUniversityFiscalYear());
115            effortCertificationDocument.setEmplid(effortCertificationDocumentBuild.getEmplid());
116            effortCertificationDocument.setEffortCertificationReportNumber(effortCertificationDocumentBuild.getEffortCertificationReportNumber());
117            effortCertificationDocument.setEffortCertificationDocumentCode(effortCertificationDocumentBuild.getEffortCertificationDocumentCode());
118    
119            // populcate the detail line of the document
120            List<EffortCertificationDetail> detailLines = effortCertificationDocument.getEffortCertificationDetailLines();
121            detailLines.clear();
122    
123            List<EffortCertificationDetailBuild> detailLinesBuild = effortCertificationDocumentBuild.getEffortCertificationDetailLinesBuild();
124            for (EffortCertificationDetailBuild detailLineBuild : detailLinesBuild) {
125                detailLines.add(new EffortCertificationDetail(detailLineBuild));
126            }
127    
128            // populate the document header of the document
129            FinancialSystemDocumentHeader documentHeader = effortCertificationDocument.getDocumentHeader();
130            documentHeader.setDocumentDescription(effortCertificationDocumentBuild.getEmplid());
131            documentHeader.setFinancialDocumentTotalAmount(EffortCertificationDocument.getDocumentTotalAmount(effortCertificationDocument));
132    
133            return true;
134        }
135    
136        /**
137         * @see org.kuali.kfs.module.ec.service.EffortCertificationDocumentService#resetEffortCertificationDetailLines(org.kuali.kfs.module.ec.document.EffortCertificationDocument)
138         */
139        @Logged
140        public void removeEffortCertificationDetailLines(EffortCertificationDocument effortCertificationDocument) {
141            Map<String, String> fieldValues = new HashMap<String, String>();
142            fieldValues.put(KFSPropertyConstants.DOCUMENT_NUMBER, effortCertificationDocument.getDocumentNumber());
143    
144            businessObjectService.deleteMatching(EffortCertificationDetail.class, fieldValues);
145        }
146    
147        /**
148         * @see org.kuali.kfs.module.ec.service.EffortCertificationDocumentService#generateSalaryExpenseTransferDocument(org.kuali.kfs.module.ec.document.EffortCertificationDocument)
149         */
150        @Logged
151        public boolean generateSalaryExpenseTransferDocument(EffortCertificationDocument effortCertificationDocument) {
152            List<LaborLedgerExpenseTransferAccountingLine> sourceAccoutingLines = this.buildSourceAccountingLines(effortCertificationDocument);
153            List<LaborLedgerExpenseTransferAccountingLine> targetAccoutingLines = this.buildTargetAccountingLines(effortCertificationDocument);
154    
155            if (sourceAccoutingLines.isEmpty() || targetAccoutingLines.isEmpty()) {
156                return true;
157            }
158    
159            String description = effortCertificationDocument.getEmplid();
160            String explanation = MessageBuilder.buildMessageWithPlaceHolder(EffortKeyConstants.MESSAGE_CREATE_SET_DOCUMENT_DESCRIPTION, effortCertificationDocument.getDocumentNumber()).toString();
161    
162            String annotation = KFSConstants.EMPTY_STRING;
163            List<String> adHocRecipients = new ArrayList<String>();
164            adHocRecipients.addAll(this.getFiscalOfficersIfAmountChanged(effortCertificationDocument));
165    
166            try {
167                laborModuleService.createAndBlankApproveSalaryExpenseTransferDocument(description, explanation, annotation, adHocRecipients, sourceAccoutingLines, targetAccoutingLines);
168            }
169            catch (WorkflowException we) {
170                LOG.error(we);
171                throw new RuntimeException(we);
172            }
173            return true;
174        }
175    
176        /**
177         * @see org.kuali.kfs.module.ec.service.EffortCertificationDocumentService#addRouteLooping(org.kuali.kfs.module.ec.document.EffortCertificationDocument)
178         */
179        @Logged
180        public void addRouteLooping(EffortCertificationDocument effortCertificationDocument) {
181            List<EffortCertificationDetail> detailLines = effortCertificationDocument.getEffortCertificationDetailLines();
182            List<AdHocRoutePerson> adHocRoutePersonList = effortCertificationDocument.getAdHocRoutePersons();
183    
184            KualiWorkflowDocument workflowDocument = effortCertificationDocument.getDocumentHeader().getWorkflowDocument();
185            String routeLevelName = workflowDocument.getCurrentRouteNodeNames();
186            Set<Person> priorApprovers = getPriorApprovers(workflowDocument);
187    
188            for (EffortCertificationDetail detailLine : detailLines) {
189                boolean hasBeenChanged = EffortCertificationDocumentRuleUtil.isPayrollAmountChangedFromPersisted(detailLine);
190                if (!hasBeenChanged) {
191                    continue;
192                }
193    
194                Account account = detailLine.getAccount();
195                String accountFiscalOfficerPersonUserId = account.getAccountFiscalOfficerUser().getPrincipalName();
196                if (StringUtils.isNotEmpty(accountFiscalOfficerPersonUserId)) {
197                    //KULEFR-206
198                    //String actionRequestOfOfficer = this.getActionRequest(routeLevelName, KFSConstants.RouteLevelNames.ACCOUNT);                
199                    AdHocRoutePerson adHocRoutePerson = this.buildAdHocRouteRecipient(accountFiscalOfficerPersonUserId, KEWConstants.ACTION_REQUEST_APPROVE_REQ);
200    
201                    this.addAdHocRoutePerson(adHocRoutePersonList, priorApprovers, adHocRoutePerson);
202                }
203    
204                Person projectDirector = contractsAndGrantsModuleService.getProjectDirectorForAccount(account);
205                if (projectDirector != null) {
206                    String accountProjectDirectorPersonUserId = projectDirector.getPrincipalName();
207                    //KULEFR-206
208                    //String actionRequestOfDirector = this.getActionRequest(routeLevelName, KFSConstants.RouteLevelNames.PROJECT_MANAGEMENT);                 
209                    AdHocRoutePerson adHocRoutePerson = this.buildAdHocRouteRecipient(accountProjectDirectorPersonUserId, KEWConstants.ACTION_REQUEST_APPROVE_REQ);
210    
211                    this.addAdHocRoutePerson(adHocRoutePersonList, priorApprovers, adHocRoutePerson);
212                }
213            }
214        }
215    
216        // add the given ad hoc route person in the list if the person is one of prior approvers and is not in the list
217        protected void addAdHocRoutePerson(Collection<AdHocRoutePerson> adHocRoutePersonList, Set<Person> priorApprovers, AdHocRoutePerson adHocRoutePerson) {
218            boolean canBeAdded = false;
219            
220            if (priorApprovers == null) {
221                canBeAdded = true;
222            }
223            else {
224                for (Person approver : priorApprovers) {
225                    if (StringUtils.equals(approver.getPrincipalName(), adHocRoutePerson.getId())) {
226                        canBeAdded = true;
227                        break;
228                    }
229                }
230            }
231    
232            if (canBeAdded) {
233                for (AdHocRoutePerson person : adHocRoutePersonList) {
234                    if (this.isSameAdHocRoutePerson(person, adHocRoutePerson)) {
235                        canBeAdded = false;
236                        break;
237                    }
238                }
239            }
240    
241            if (canBeAdded) {
242                adHocRoutePersonList.add(adHocRoutePerson);
243            }
244        }
245        
246        protected boolean isSameAdHocRoutePerson(AdHocRoutePerson person1, AdHocRoutePerson person2) {
247            if(person1 == null || person2 == null) {
248                return false;
249            }
250            
251            boolean isSameAdHocRoutePerson = StringUtils.equals(person1.getId(), person2.getId());
252            isSameAdHocRoutePerson &= person1.getType().equals(person2.getType());
253            isSameAdHocRoutePerson &= StringUtils.equals(person1.getActionRequested(), person2.getActionRequested());
254                   
255            return isSameAdHocRoutePerson;
256        }
257    
258        protected Set<Person> getPriorApprovers(KualiWorkflowDocument workflowDocument) {
259            Set<Person> priorApprovers = null;
260            try {
261                priorApprovers = workflowDocument.getAllPriorApprovers();
262            }
263            catch (WorkflowException e) {
264                e.printStackTrace();
265            }
266    
267            return priorApprovers;
268        }
269    
270        /**
271         * determine the action request according to the current route level and expected route level
272         * 
273         * @param routeLevelName the current route level
274         * @param expectedRouteLevelName the expected route level
275         * @return the action request determined from the current route level and expected route level
276         */
277        protected String getActionRequest(String routeLevelName, String expectedRouteLevelName) {
278            boolean isExpectedRouteLevel = StringUtils.equals(routeLevelName, expectedRouteLevelName);
279            return isExpectedRouteLevel ? KEWConstants.ACTION_REQUEST_APPROVE_REQ : KEWConstants.ACTION_REQUEST_ACKNOWLEDGE_REQ;
280        }
281    
282        /**
283         * build an adhoc route recipient from the given person user id and action request
284         * 
285         * @param personUserId the given person user id
286         * @param actionRequest the given action request
287         * @return an adhoc route recipient built from the given information
288         */
289        protected AdHocRoutePerson buildAdHocRouteRecipient(String personUserId, String actionRequest) {
290            AdHocRoutePerson adHocRoutePerson = new AdHocRoutePerson();
291            adHocRoutePerson.setActionRequested(actionRequest);
292            adHocRoutePerson.setId(personUserId);
293    
294            return adHocRoutePerson;
295        }
296    
297        /**
298         * build the source accounting lines for a salary expense transfer document from the given effort certification document. In the
299         * holder, the first item is source accounting line list and the second the target accounting line list.
300         * 
301         * @param effortCertificationDocument the given effort certification document
302         * @return the source accounting lines for a salary expense transfer document built from the given effort certification document
303         */
304        protected List<LaborLedgerExpenseTransferAccountingLine> buildSourceAccountingLines(EffortCertificationDocument effortCertificationDocument) {
305            List<LaborLedgerExpenseTransferAccountingLine> sourceAccountingLines = new ArrayList<LaborLedgerExpenseTransferAccountingLine>();
306    
307            List<EffortCertificationDetail> effortCertificationDetailLines = effortCertificationDocument.getEffortCertificationDetailLines();
308            for (EffortCertificationDetail detailLine : effortCertificationDetailLines) {
309                if (this.getDifference(detailLine).isPositive()) {
310                    LaborLedgerExpenseTransferSourceAccountingLine sourceLine = kualiModuleService.getResponsibleModuleService(LaborLedgerExpenseTransferSourceAccountingLine.class).createNewObjectFromExternalizableClass(LaborLedgerExpenseTransferSourceAccountingLine.class);
311                    this.addAccountingLineIntoList(sourceAccountingLines, sourceLine, effortCertificationDocument, detailLine);
312                }
313            }
314            return sourceAccountingLines;
315        }
316    
317        /**
318         * build the target accounting lines for a salary expense transfer document from the given effort certification document. In the
319         * holder, the first item is source accounting line list and the second the target accounting line list.
320         * 
321         * @param effortCertificationDocument the given effort certification document
322         * @return the target accounting lines for a salary expense transfer document built from the given effort certification document
323         */
324        protected List<LaborLedgerExpenseTransferAccountingLine> buildTargetAccountingLines(EffortCertificationDocument effortCertificationDocument) {
325            List<LaborLedgerExpenseTransferAccountingLine> targetAccountingLines = new ArrayList<LaborLedgerExpenseTransferAccountingLine>();
326    
327            List<EffortCertificationDetail> effortCertificationDetailLines = effortCertificationDocument.getEffortCertificationDetailLines();
328            for (EffortCertificationDetail detailLine : effortCertificationDetailLines) {
329                if (this.getDifference(detailLine).isNegative()) {
330                    LaborLedgerExpenseTransferTargetAccountingLine targetLine = kualiModuleService.getResponsibleModuleService(LaborLedgerExpenseTransferTargetAccountingLine.class).createNewObjectFromExternalizableClass(LaborLedgerExpenseTransferTargetAccountingLine.class);
331                    this.addAccountingLineIntoList(targetAccountingLines, targetLine, effortCertificationDocument, detailLine);
332                }
333            }
334            return targetAccountingLines;
335        }
336    
337        /**
338         * get all fiscal officers of the detail line accounts where the salary amounts are changed
339         * 
340         * @param effortCertificationDocument the given document that contains the detail lines
341         * @return all fiscal officers of the detail line accounts where the salary amounts are changed
342         */
343        protected Set<String> getFiscalOfficersIfAmountChanged(EffortCertificationDocument effortCertificationDocument) {
344            Set<String> fiscalOfficers = new HashSet<String>();
345    
346            List<EffortCertificationDetail> effortCertificationDetailLines = effortCertificationDocument.getEffortCertificationDetailLines();
347            for (EffortCertificationDetail detailLine : effortCertificationDetailLines) {
348                if (this.getDifference(detailLine).isNonZero()) {
349                    Account account = detailLine.getAccount();
350                    String accountFiscalOfficerPersonUserId = account.getAccountFiscalOfficerUser().getPrincipalName();
351    
352                    if (StringUtils.isEmpty(accountFiscalOfficerPersonUserId)) {
353                        fiscalOfficers.add(accountFiscalOfficerPersonUserId);
354                    }
355                }
356            }
357            return fiscalOfficers;
358        }
359    
360        /**
361         * add a new accounting line into the given accounting line list. The accounting line is generated from the given detail line
362         * 
363         * @param accountingLines a list of accounting lines
364         * @param clazz the specified class of the accounting line
365         * @param effortCertificationDocument the given effort certification document that contains the given detail line
366         * @param detailLine the given detail line that is used to generate an accounting line
367         */
368        protected void addAccountingLineIntoList(List<LaborLedgerExpenseTransferAccountingLine> accountingLineList, LaborLedgerExpenseTransferAccountingLine accountingLine, EffortCertificationDocument effortCertificationDocument, EffortCertificationDetail detailLine) {
369            accountingLine.setSequenceNumber(accountingLineList.size() + 1);
370    
371            this.populateAccountingLine(effortCertificationDocument, detailLine, accountingLine);
372            accountingLineList.add(accountingLine);
373        }
374    
375        /**
376         * populate an accounting line from the given detail line
377         * 
378         * @param effortCertificationDocument the given effort certification document that contains the given detail line
379         * @param detailLine the given detail line
380         * @param accountingLine the accounting line needed to be populated
381         */
382        protected void populateAccountingLine(EffortCertificationDocument effortCertificationDocument, EffortCertificationDetail detailLine, LaborLedgerExpenseTransferAccountingLine accountingLine) {
383            if (detailLine.isAccountExpiredOverride()) {
384                AccountingLineOverride override = EffortCertificationDetailLineOverride.determineNeededOverrides(detailLine);
385                accountingLine.setOverrideCode(override.getCode());
386            }
387    
388            accountingLine.setChartOfAccountsCode(detailLine.getChartOfAccountsCode());
389            accountingLine.setAccountNumber(detailLine.getAccountNumber());
390            accountingLine.setSubAccountNumber(detailLine.getSubAccountNumber());
391    
392            accountingLine.setPostingYear(detailLine.getUniversityFiscalYear());
393            accountingLine.setFinancialObjectCode(detailLine.getFinancialObjectCode());
394            accountingLine.setBalanceTypeCode(KFSConstants.BALANCE_TYPE_ACTUAL);
395    
396            accountingLine.setAmount(this.getDifference(detailLine).abs());
397    
398            accountingLine.setFinancialSubObjectCode(null);
399            accountingLine.setProjectCode(null);
400            accountingLine.setOrganizationReferenceId(null);
401    
402            accountingLine.setEmplid(effortCertificationDocument.getEmplid());
403            accountingLine.setPositionNumber(detailLine.getPositionNumber());
404            accountingLine.setPayrollTotalHours(BigDecimal.ZERO);
405    
406            EffortCertificationReportDefinition reportDefinition = effortCertificationDocument.getEffortCertificationReportDefinition();
407            accountingLine.setPayrollEndDateFiscalYear(reportDefinition.getExpenseTransferFiscalYear());
408            accountingLine.setPayrollEndDateFiscalPeriodCode(reportDefinition.getExpenseTransferFiscalPeriodCode());
409        }
410    
411        /**
412         * get the difference between the original amount and updated amount of the given detail line
413         * 
414         * @param detailLine the given detail line
415         * @return the difference between the original amount and updated amount of the given detail line
416         */
417        protected KualiDecimal getDifference(EffortCertificationDetail detailLine) {
418            return detailLine.getEffortCertificationOriginalPayrollAmount().subtract(detailLine.getEffortCertificationPayrollAmount());
419        }
420    
421        /**
422         * Sets the laborModuleService attribute value.
423         * 
424         * @param laborModuleService The laborModuleService to set.
425         */
426        public void setLaborModuleService(LaborModuleService laborModuleService) {
427            this.laborModuleService = laborModuleService;
428        }
429    
430        /**
431         * Sets the documentService attribute value.
432         * 
433         * @param documentService The documentService to set.
434         */
435        public void setDocumentService(DocumentService documentService) {
436            this.documentService = documentService;
437        }
438    
439        /**
440         * Sets the businessObjectService attribute value.
441         * 
442         * @param businessObjectService The businessObjectService to set.
443         */
444        public void setBusinessObjectService(BusinessObjectService businessObjectService) {
445            this.businessObjectService = businessObjectService;
446        }
447    
448        /**
449         * Sets the contractsAndGrantsModuleService attribute value.
450         * 
451         * @param contractsAndGrantsModuleService The contractsAndGrantsModuleService to set.
452         */
453        public void setContractsAndGrantsModuleService(ContractsAndGrantsModuleService contractsAndGrantsModuleService) {
454            this.contractsAndGrantsModuleService = contractsAndGrantsModuleService;
455        }
456    
457        /**
458         * Sets the kualiModuleService attribute value.
459         * 
460         * @param kualiModuleService The kualiModuleService to set.
461         */
462        public void setKualiModuleService(KualiModuleService kualiModuleService) {
463            this.kualiModuleService = kualiModuleService;
464        }
465    }