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 }