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 }