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.purap.document.service.impl; 017 018 import java.util.HashMap; 019 import java.util.Iterator; 020 import java.util.List; 021 import java.util.Map; 022 import java.util.Set; 023 024 import org.kuali.kfs.coa.businessobject.Account; 025 import org.kuali.kfs.coa.service.AccountService; 026 import org.kuali.kfs.gl.batch.ScrubberStep; 027 import org.kuali.kfs.module.purap.PurapConstants; 028 import org.kuali.kfs.module.purap.PurapKeyConstants; 029 import org.kuali.kfs.module.purap.PurapPropertyConstants; 030 import org.kuali.kfs.module.purap.PurapConstants.AccountsPayableSharedStatuses; 031 import org.kuali.kfs.module.purap.PurapConstants.PaymentRequestStatuses; 032 import org.kuali.kfs.module.purap.businessobject.CreditMemoItem; 033 import org.kuali.kfs.module.purap.businessobject.ItemType; 034 import org.kuali.kfs.module.purap.businessobject.PaymentRequestItem; 035 import org.kuali.kfs.module.purap.businessobject.PurApAccountingLineBase; 036 import org.kuali.kfs.module.purap.businessobject.PurchaseOrderItem; 037 import org.kuali.kfs.module.purap.document.AccountsPayableDocument; 038 import org.kuali.kfs.module.purap.document.PaymentRequestDocument; 039 import org.kuali.kfs.module.purap.document.PurchaseOrderDocument; 040 import org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument; 041 import org.kuali.kfs.module.purap.document.VendorCreditMemoDocument; 042 import org.kuali.kfs.module.purap.document.service.AccountsPayableDocumentSpecificService; 043 import org.kuali.kfs.module.purap.document.service.AccountsPayableService; 044 import org.kuali.kfs.module.purap.document.service.PurapService; 045 import org.kuali.kfs.module.purap.document.service.PurchaseOrderService; 046 import org.kuali.kfs.module.purap.service.PurapAccountingService; 047 import org.kuali.kfs.module.purap.service.PurapGeneralLedgerService; 048 import org.kuali.kfs.module.purap.util.ExpiredOrClosedAccount; 049 import org.kuali.kfs.module.purap.util.ExpiredOrClosedAccountEntry; 050 import org.kuali.kfs.sys.KFSConstants; 051 import org.kuali.kfs.sys.businessobject.SourceAccountingLine; 052 import org.kuali.kfs.sys.context.SpringContext; 053 import org.kuali.kfs.sys.service.impl.KfsParameterConstants; 054 import org.kuali.rice.kim.bo.Person; 055 import org.kuali.rice.kim.service.PersonService; 056 import org.kuali.rice.kns.UserSession; 057 import org.kuali.rice.kns.bo.Note; 058 import org.kuali.rice.kns.service.BusinessObjectService; 059 import org.kuali.rice.kns.service.DateTimeService; 060 import org.kuali.rice.kns.service.DocumentService; 061 import org.kuali.rice.kns.service.NoteService; 062 import org.kuali.rice.kns.service.ParameterService; 063 import org.kuali.rice.kns.util.GlobalVariables; 064 import org.kuali.rice.kns.util.KualiDecimal; 065 import org.kuali.rice.kns.util.ObjectUtils; 066 import org.kuali.rice.kns.workflow.service.KualiWorkflowDocument; 067 import org.kuali.rice.kns.workflow.service.WorkflowDocumentService; 068 import org.springframework.transaction.annotation.Transactional; 069 070 071 @Transactional 072 public class AccountsPayableServiceImpl implements AccountsPayableService { 073 074 protected PurapAccountingService purapAccountingService; 075 protected PurapGeneralLedgerService purapGeneralLedgerService; 076 protected DocumentService documentService; 077 protected PurapService purapService; 078 protected ParameterService parameterService; 079 protected DateTimeService dateTimeService; 080 protected PurchaseOrderService purchaseOrderService; 081 protected AccountService accountService; 082 083 public void setParameterService(ParameterService parameterService) { 084 this.parameterService = parameterService; 085 } 086 087 public void setPurapService(PurapService purapService) { 088 this.purapService = purapService; 089 } 090 091 public void setPurapAccountingService(PurapAccountingService purapAccountingService) { 092 this.purapAccountingService = purapAccountingService; 093 } 094 095 public void setPurapGeneralLedgerService(PurapGeneralLedgerService purapGeneralLedgerService) { 096 this.purapGeneralLedgerService = purapGeneralLedgerService; 097 } 098 099 public void setDocumentService(DocumentService documentService) { 100 this.documentService = documentService; 101 } 102 103 public void setDateTimeService(DateTimeService dateTimeService) { 104 this.dateTimeService = dateTimeService; 105 } 106 107 public void setPurchaseOrderService(PurchaseOrderService purchaseOrderService) { 108 this.purchaseOrderService = purchaseOrderService; 109 } 110 111 public void setAccountService(AccountService accountService) { 112 this.accountService = accountService; 113 } 114 115 /** 116 * @see org.kuali.kfs.module.purap.document.service.AccountsPayableService#getExpiredOrClosedAccountList(org.kuali.kfs.module.purap.document.AccountsPayableDocument) 117 */ 118 public HashMap<String, ExpiredOrClosedAccountEntry> getExpiredOrClosedAccountList(AccountsPayableDocument document) { 119 120 // Retrieve a list of accounts and replacement accounts, where accounts or closed or expired. 121 HashMap<String, ExpiredOrClosedAccountEntry> expiredOrClosedAccounts = expiredOrClosedAccountsList(document); 122 123 return expiredOrClosedAccounts; 124 } 125 126 /** 127 * @see org.kuali.kfs.module.purap.document.service.AccountsPayableService#generateExpiredOrClosedAccountNote(org.kuali.kfs.module.purap.document.AccountsPayableDocument, 128 * java.util.HashMap) 129 */ 130 public void generateExpiredOrClosedAccountNote(AccountsPayableDocument document, HashMap<String, ExpiredOrClosedAccountEntry> expiredOrClosedAccountList) { 131 132 // create a note of all the replacement accounts 133 if (ObjectUtils.isNotNull(expiredOrClosedAccountList) && !expiredOrClosedAccountList.isEmpty()) { 134 addContinuationAccountsNote(document, expiredOrClosedAccountList); 135 } 136 137 } 138 139 /** 140 * @see org.kuali.kfs.module.purap.document.service.AccountsPayableService#generateExpiredOrClosedAccountWarning(org.kuali.kfs.module.purap.document.AccountsPayableDocument) 141 */ 142 public void generateExpiredOrClosedAccountWarning(AccountsPayableDocument document) { 143 144 // get user 145 Person user = GlobalVariables.getUserSession().getPerson(); 146 147 // get parameter to see if fiscal officers may see the continuation account warning 148 String showContinuationAccountWaringFO = parameterService.getParameterValue(KfsParameterConstants.PURCHASING_DOCUMENT.class, PurapConstants.PURAP_AP_SHOW_CONTINUATION_ACCOUNT_WARNING_FISCAL_OFFICERS); 149 150 // get parameter to see if ap users may see the continuation account warning 151 String showContinuationAccountWaringAP = parameterService.getParameterValue(KfsParameterConstants.PURCHASING_DOCUMENT.class, PurapConstants.PURAP_AP_SHOW_CONTINUATION_ACCOUNT_WARNING_AP_USERS); 152 153 // versus doing it in their respective documents (preq, credit memo) 154 // document is past full entry and 155 // user is a fiscal officer and a system parameter is set to allow viewing 156 // and if the continuation account indicator is set 157 if (isFiscalUser(document, user) && "Y".equals(showContinuationAccountWaringFO) && (document.isContinuationAccountIndicator())) { 158 GlobalVariables.getMessageList().add(PurapKeyConstants.MESSAGE_CLOSED_OR_EXPIRED_ACCOUNTS_REPLACED); 159 } 160 161 // document is past full entry and 162 // user is an AP User and a system parameter is set to allow viewing 163 // and if the continuation account indicator is set 164 if (isAPUser(document, user) && "Y".equals(showContinuationAccountWaringAP) && (document.isContinuationAccountIndicator())) { 165 GlobalVariables.getMessageList().add(PurapKeyConstants.MESSAGE_CLOSED_OR_EXPIRED_ACCOUNTS_REPLACED); 166 } 167 168 } 169 170 /** 171 * @see org.kuali.kfs.module.purap.document.service.AccountsPayableService#processExpiredOrClosedAccount(org.kuali.kfs.module.purap.businessobject.PurApAccountingLineBase, 172 * java.util.HashMap) 173 */ 174 public void processExpiredOrClosedAccount(PurApAccountingLineBase acctLineBase, HashMap<String, ExpiredOrClosedAccountEntry> expiredOrClosedAccountList) { 175 176 ExpiredOrClosedAccountEntry accountEntry = null; 177 String acctKey = acctLineBase.getChartOfAccountsCode() + "-" + acctLineBase.getAccountNumber(); 178 179 if (expiredOrClosedAccountList.containsKey(acctKey)) { 180 181 accountEntry = expiredOrClosedAccountList.get(acctKey); 182 183 if (accountEntry.getOriginalAccount().isContinuationAccountMissing() == false) { 184 acctLineBase.setChartOfAccountsCode(accountEntry.getReplacementAccount().getChartOfAccountsCode()); 185 acctLineBase.setAccountNumber(accountEntry.getReplacementAccount().getAccountNumber()); 186 acctLineBase.refreshReferenceObject("chart"); 187 acctLineBase.refreshReferenceObject("account"); 188 } 189 } 190 } 191 192 /** 193 * Creates and adds a note indicating accounts replaced and what they replaced and attaches it to the document. 194 * 195 * @param document The accounts payable document to which we're adding the note. 196 * @param accounts The HashMap where the keys are the string representations of the chart and account of the 197 * original account and the values are the ExpiredOrClosedAccountEntry. 198 */ 199 protected void addContinuationAccountsNote(AccountsPayableDocument document, HashMap<String, ExpiredOrClosedAccountEntry> accounts) { 200 String noteText; 201 StringBuffer sb = new StringBuffer(""); 202 ExpiredOrClosedAccountEntry accountEntry = null; 203 ExpiredOrClosedAccount originalAccount = null; 204 ExpiredOrClosedAccount replacementAccount = null; 205 206 // List the entries using entrySet() 207 Set entries = accounts.entrySet(); 208 Iterator it = entries.iterator(); 209 210 // loop through the accounts found to be expired/closed and add if they have a continuation account 211 while (it.hasNext()) { 212 Map.Entry entry = (Map.Entry) it.next(); 213 accountEntry = (ExpiredOrClosedAccountEntry) entry.getValue(); 214 originalAccount = accountEntry.getOriginalAccount(); 215 replacementAccount = accountEntry.getReplacementAccount(); 216 217 // only print out accounts that were replaced and not missing a continuation account 218 if (originalAccount.isContinuationAccountMissing() == false) { 219 sb.append(" Account " + originalAccount.getAccountString() + " was replaced with account " + replacementAccount.getAccountString() + " ; "); 220 } 221 222 } 223 224 // if a note was created, add it to the document 225 if (sb.toString().length() > 0) { 226 try { 227 Note resetNote = documentService.createNoteFromDocument(document, sb.toString()); 228 documentService.addNoteToDocument(document, resetNote); 229 } 230 catch (Exception e) { 231 throw new RuntimeException(PurapConstants.REQ_UNABLE_TO_CREATE_NOTE + " " + e); 232 } 233 } 234 } 235 236 /** 237 * Gets the replacement account for the specified closed account. 238 * In this case it's the continuation account of the the specified account. 239 * 240 * @param account the specified account which is closed. 241 * @document the document the account is associated with. 242 * @return the replacement account for the specified account. 243 */ 244 protected Account getReplaceAccountForClosedAccount(Account account, AccountsPayableDocument document) { 245 if (account == null) return null; // this should never happen 246 Account continueAccount = accountService.getByPrimaryId(account.getContinuationFinChrtOfAcctCd(), account.getContinuationAccountNumber()); 247 return continueAccount; 248 } 249 250 /** 251 * Gets the replacement account for the specified expired account. 252 * In this case it's the continuation account of the the specified account. 253 * 254 * @param account the specified account which is expired. 255 * @document the document the account is associated with. 256 * @return the replacement account for the specified account. 257 */ 258 protected Account getReplaceAccountForExpiredAccount(Account account, AccountsPayableDocument document) { 259 if (account == null) return null; // this should never happen 260 Account continueAccount = accountService.getByPrimaryId(account.getContinuationFinChrtOfAcctCd(), account.getContinuationAccountNumber()); 261 return continueAccount; 262 } 263 264 /** 265 * Generates a list of replacement accounts for expired or closed accounts, as well as for expired/closed accounts without a continuation account. 266 * 267 * @param document The accounts payable document from which we're obtaining the purchase order id to be used 268 * to obtain the purchase order document, whose accounts we'll use to generate the list of 269 * replacement accounts for expired or closed accounts. 270 * @return The HashMap where the keys are the string representations of the chart and account 271 * of the original account and the values are the ExpiredOrClosedAccountEntry. 272 */ 273 protected HashMap<String, ExpiredOrClosedAccountEntry> expiredOrClosedAccountsList(AccountsPayableDocument document) { 274 // retrieve po from apdoc 275 PurchaseOrderDocument po = document.getPurchaseOrderDocument(); 276 if (po == null && document instanceof VendorCreditMemoDocument) { 277 PaymentRequestDocument preq = ((VendorCreditMemoDocument)document).getPaymentRequestDocument(); 278 if (preq == null) return null; // this should never happen 279 po = ((VendorCreditMemoDocument)document).getPaymentRequestDocument().getPurchaseOrderDocument(); 280 } 281 if (po == null) return null; // this should never happen 282 283 // initialize 284 List<SourceAccountingLine> acctLines = purapAccountingService.generateSummary(po.getItemsActiveOnly()); 285 HashMap<String, ExpiredOrClosedAccountEntry> eocAcctMap = new HashMap<String, ExpiredOrClosedAccountEntry>(); 286 287 // loop through accounting lines 288 for (SourceAccountingLine acctLine : acctLines) { 289 Account account = accountService.getByPrimaryId(acctLine.getChartOfAccountsCode(), acctLine.getAccountNumber()); 290 Account repAccount = null; 291 boolean replace = false; 292 293 // 1. if the account is closed, get the continuation account as replacement 294 if (!account.isActive()) { 295 repAccount = getReplaceAccountForClosedAccount(account, document); 296 replace = true; 297 } 298 // 2. if the account is C&G and is expired for more than 90 days, get the continuation account as replacement 299 else if (account.isExpired()) { 300 // retrieve extension limit (grace period) 301 String expirationExtensionDays = parameterService.getParameterValue(ScrubberStep.class, KFSConstants.SystemGroupParameterNames.GL_SCRUBBER_VALIDATION_DAYS_OFFSET); 302 int expirationExtensionDaysInt = 90; // default to 90 days (approximately 3 months) 303 if (expirationExtensionDays.trim().length() > 0) { 304 expirationExtensionDaysInt = new Integer(expirationExtensionDays).intValue(); 305 } 306 // if account is C&G and expired beyond grace period then get replacement 307 if ((account.isForContractsAndGrants() && dateTimeService.dateDiff(account.getAccountExpirationDate(), dateTimeService.getCurrentDate(), true) > expirationExtensionDaysInt)) { 308 repAccount = getReplaceAccountForExpiredAccount(account, document); 309 replace = true; 310 } 311 // otherwise if the account is not C&G, or it's expired within the grace period, do nothing 312 } 313 314 // if replacement needed, set up ExpiredOrClosedAccount entry and add it to the eocAcctMap 315 if (replace) { 316 ExpiredOrClosedAccountEntry eocAcctEntry = new ExpiredOrClosedAccountEntry(); 317 ExpiredOrClosedAccount originAcct = new ExpiredOrClosedAccount(acctLine.getChartOfAccountsCode(), acctLine.getAccountNumber(), acctLine.getSubAccountNumber()); 318 ExpiredOrClosedAccount replaceAcct = null; 319 if (repAccount == null) { 320 replaceAcct = new ExpiredOrClosedAccount(); 321 originAcct.setContinuationAccountMissing(true); 322 } 323 else { 324 replaceAcct = new ExpiredOrClosedAccount(repAccount.getChartOfAccountsCode(), repAccount.getAccountNumber(), acctLine.getSubAccountNumber()); 325 } 326 eocAcctEntry.setOriginalAccount(originAcct); 327 eocAcctEntry.setReplacementAccount(replaceAcct); 328 eocAcctMap.put(createChartAccountString(originAcct), eocAcctEntry); 329 } 330 } 331 332 return eocAcctMap; 333 } 334 335 /** 336 * Generates a list of replacement accounts for expired or closed accounts, as well as for expired/closed accounts without a continuation account. 337 * 338 * @param document The purchase order document whose accounts we'll use to generate the list of 339 * replacement accounts for expired or closed accounts. 340 * @return The HashMap where the keys are the string representations of the chart and account 341 * of the original account and the values are the ExpiredOrClosedAccountEntry. 342 */ 343 public HashMap<String, ExpiredOrClosedAccountEntry> expiredOrClosedAccountsList(PurchaseOrderDocument po) { 344 HashMap<String, ExpiredOrClosedAccountEntry> list = new HashMap<String, ExpiredOrClosedAccountEntry>(); 345 ExpiredOrClosedAccountEntry entry = null; 346 ExpiredOrClosedAccount originalAcct = null; 347 ExpiredOrClosedAccount replaceAcct = null; 348 String chartAccount = null; 349 350 if (po != null) { 351 // get list of active accounts 352 List<SourceAccountingLine> accountList = purapAccountingService.generateSummary(po.getItemsActiveOnly()); 353 354 // loop through accounts 355 for (SourceAccountingLine poAccountingLine : accountList) { 356 Account account = accountService.getByPrimaryId(poAccountingLine.getChartOfAccountsCode(), poAccountingLine.getAccountNumber()); 357 entry = new ExpiredOrClosedAccountEntry(); 358 originalAcct = new ExpiredOrClosedAccount(poAccountingLine.getChartOfAccountsCode(), poAccountingLine.getAccountNumber(), poAccountingLine.getSubAccountNumber()); 359 360 if (!account.isActive()) { 361 // 1. if the account is closed, get the continuation account and add it to the list 362 Account continuationAccount = accountService.getByPrimaryId(account.getContinuationFinChrtOfAcctCd(), account.getContinuationAccountNumber()); 363 364 if (continuationAccount == null) { 365 replaceAcct = new ExpiredOrClosedAccount(); 366 originalAcct.setContinuationAccountMissing(true); 367 368 entry.setOriginalAccount(originalAcct); 369 entry.setReplacementAccount(replaceAcct); 370 371 list.put(createChartAccountString(originalAcct), entry); 372 } 373 else { 374 replaceAcct = new ExpiredOrClosedAccount(continuationAccount.getChartOfAccountsCode(), continuationAccount.getAccountNumber(), poAccountingLine.getSubAccountNumber()); 375 376 entry.setOriginalAccount(originalAcct); 377 entry.setReplacementAccount(replaceAcct); 378 379 list.put(createChartAccountString(originalAcct), entry); 380 } 381 // 2. if the account is expired and the current date is <= 90 days from the expiration date, do nothing 382 // 3. if the account is expired and the current date is > 90 days from the expiration date, get the continuation 383 // account and add it to the list 384 } 385 else if (account.isExpired()) { 386 Account continuationAccount = accountService.getByPrimaryId(account.getContinuationFinChrtOfAcctCd(), account.getContinuationAccountNumber()); 387 String expirationExtensionDays = parameterService.getParameterValue(ScrubberStep.class, KFSConstants.SystemGroupParameterNames.GL_SCRUBBER_VALIDATION_DAYS_OFFSET); 388 int expirationExtensionDaysInt = 3 * 30; // default to 90 days (approximately 3 months) 389 390 if (expirationExtensionDays.trim().length() > 0) { 391 392 expirationExtensionDaysInt = new Integer(expirationExtensionDays).intValue(); 393 } 394 395 // if account is C&G and expired then add to list. 396 if ((account.isForContractsAndGrants() && dateTimeService.dateDiff(account.getAccountExpirationDate(), dateTimeService.getCurrentDate(), true) > expirationExtensionDaysInt)) { 397 398 if (continuationAccount == null) { 399 replaceAcct = new ExpiredOrClosedAccount(); 400 originalAcct.setContinuationAccountMissing(true); 401 402 entry.setOriginalAccount(originalAcct); 403 entry.setReplacementAccount(replaceAcct); 404 405 list.put(createChartAccountString(originalAcct), entry); 406 } 407 else { 408 replaceAcct = new ExpiredOrClosedAccount(continuationAccount.getChartOfAccountsCode(), continuationAccount.getAccountNumber(), poAccountingLine.getSubAccountNumber()); 409 410 entry.setOriginalAccount(originalAcct); 411 entry.setReplacementAccount(replaceAcct); 412 413 list.put(createChartAccountString(originalAcct), entry); 414 } 415 } 416 417 // if account is not C&G, use the same account, do not replace 418 } 419 } 420 } 421 return list; 422 } 423 424 /** 425 * Creates a chart-account string. 426 * 427 * @param ecAccount The account whose chart and account number we're going to use to create the resulting String for this method. 428 * @return The string representing the chart and account number of the given ecAccount. 429 */ 430 protected String createChartAccountString(ExpiredOrClosedAccount ecAccount) { 431 StringBuffer buff = new StringBuffer(""); 432 433 buff.append(ecAccount.getChartOfAccountsCode()); 434 buff.append("-"); 435 buff.append(ecAccount.getAccountNumber()); 436 437 return buff.toString(); 438 } 439 440 /** 441 * Determines if the user is a fiscal officer. Currently this only checks the doc and workflow status for approval requested 442 * 443 * @param document The document to be used to check the status code and whether the workflow approval is requested. 444 * @param user The current user. 445 * @return boolean true if the user is a fiscal officer. 446 */ 447 protected boolean isFiscalUser(AccountsPayableDocument document, Person user) { 448 boolean isFiscalUser = false; 449 450 if (PaymentRequestStatuses.AWAITING_FISCAL_REVIEW.equals(document.getStatusCode()) && document.getDocumentHeader().getWorkflowDocument().isApprovalRequested()) { 451 isFiscalUser = true; 452 } 453 454 return isFiscalUser; 455 } 456 457 /** 458 * Determines if the user is an AP user. Currently this only checks the doc and workflow status for approval requested 459 * 460 * @param document The document to be used to check the status code and whether the workflow approval is requested. 461 * @param user The current user. 462 * @return boolean true if the user is an AP User. 463 */ 464 protected boolean isAPUser(AccountsPayableDocument document, Person user) { 465 boolean isFiscalUser = false; 466 467 if ((PaymentRequestStatuses.AWAITING_ACCOUNTS_PAYABLE_REVIEW.equals(document.getStatusCode()) && 468 document.getDocumentHeader().getWorkflowDocument().isApprovalRequested()) || 469 PaymentRequestStatuses.IN_PROCESS.equals(document.getStatusCode())) { 470 isFiscalUser = true; 471 } 472 473 return isFiscalUser; 474 } 475 476 /** 477 * @see org.kuali.kfs.module.purap.document.service.AccountsPayableService#cancelAccountsPayableDocument(org.kuali.kfs.module.purap.document.AccountsPayableDocument, java.lang.String) 478 */ 479 public void cancelAccountsPayableDocument(AccountsPayableDocument apDocument, String currentNodeName) { 480 if (purapService.isFullDocumentEntryCompleted(apDocument)) { 481 purapGeneralLedgerService.generateEntriesCancelAccountsPayableDocument(apDocument); 482 } 483 AccountsPayableDocumentSpecificService accountsPayableDocumentSpecificService = apDocument.getDocumentSpecificService(); 484 accountsPayableDocumentSpecificService.updateStatusByNode(currentNodeName, apDocument); 485 apDocument.refreshReferenceObject(PurapPropertyConstants.STATUS); 486 487 // close/reopen purchase order. 488 accountsPayableDocumentSpecificService.takePurchaseOrderCancelAction(apDocument); 489 } 490 491 /** 492 * @see org.kuali.kfs.module.purap.document.service.AccountsPayableService#cancelAccountsPayableDocumentByCheckingDocumentStatus(org.kuali.kfs.module.purap.document.AccountsPayableDocument, java.lang.String) 493 */ 494 public void cancelAccountsPayableDocumentByCheckingDocumentStatus(AccountsPayableDocument document, String noteText) throws Exception { 495 DocumentService documentService = SpringContext.getBean(DocumentService.class); 496 497 if (AccountsPayableSharedStatuses.IN_PROCESS.equals(document.getStatusCode())) { 498 //prior to submit, just call regular cancel logic 499 documentService.cancelDocument(document, noteText); 500 } 501 else if (AccountsPayableSharedStatuses.AWAITING_ACCOUNTS_PAYABLE_REVIEW.equals(document.getStatusCode())) { 502 //while awaiting AP approval, just call regular disapprove logic as user will have action request 503 documentService.disapproveDocument(document, noteText); 504 } 505 else { 506 UserSession originalUserSession = GlobalVariables.getUserSession(); 507 KualiWorkflowDocument originalWorkflowDocument = document.getDocumentHeader().getWorkflowDocument(); 508 //any other time, perform special logic to cancel the document 509 if (!document.getDocumentHeader().getWorkflowDocument().stateIsFinal()) { 510 try { 511 // person canceling may not have an action requested on the document 512 Person userRequestedCancel = SpringContext.getBean(PersonService.class).getPerson(document.getLastActionPerformedByPersonId()); 513 GlobalVariables.setUserSession(new UserSession(KFSConstants.SYSTEM_USER)); 514 515 WorkflowDocumentService workflowDocumentService = SpringContext.getBean(WorkflowDocumentService.class); 516 KualiWorkflowDocument newWorkflowDocument = workflowDocumentService.createWorkflowDocument(Long.valueOf(document.getDocumentNumber()), GlobalVariables.getUserSession().getPerson()); 517 document.getDocumentHeader().setWorkflowDocument(newWorkflowDocument); 518 documentService.superUserDisapproveDocument(document, "Document Cancelled by user " + originalUserSession.getPerson().getName() + " (" + originalUserSession.getPerson().getPrincipalName() + ") per request of user " + userRequestedCancel.getName() + " (" + userRequestedCancel.getPrincipalName() + ")"); 519 } 520 finally { 521 GlobalVariables.setUserSession(originalUserSession); 522 document.getDocumentHeader().setWorkflowDocument(originalWorkflowDocument); 523 } 524 } 525 else { 526 // call gl method here (no reason for post processing since workflow done) 527 SpringContext.getBean(AccountsPayableService.class).cancelAccountsPayableDocument(document, ""); 528 document.getDocumentHeader().getWorkflowDocument().logDocumentAction("Document Cancelled by user " + originalUserSession.getPerson().getName() + " (" + originalUserSession.getPerson().getPrincipalName() + ")"); 529 } 530 } 531 532 Note noteObj = documentService.createNoteFromDocument(document, noteText); 533 documentService.addNoteToDocument(document, noteObj); 534 SpringContext.getBean(NoteService.class).save(noteObj); 535 } 536 537 /** 538 * @see org.kuali.kfs.module.purap.document.service.AccountsPayableService#performLogicForFullEntryCompleted(org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument) 539 */ 540 public void performLogicForFullEntryCompleted(PurchasingAccountsPayableDocument purapDocument) { 541 AccountsPayableDocument apDocument = (AccountsPayableDocument)purapDocument; 542 AccountsPayableDocumentSpecificService accountsPayableDocumentSpecificService = apDocument.getDocumentSpecificService(); 543 544 // eliminate unentered items 545 purapService.deleteUnenteredItems(apDocument); 546 547 // change accounts from percents to dollars 548 purapAccountingService.updateAccountAmounts(apDocument); 549 550 //set the AP approval date always when the GL entries are created (treated more of an AP processed date) 551 apDocument.setAccountsPayableApprovalTimestamp(dateTimeService.getCurrentTimestamp()); 552 553 // save for persistence 554 SpringContext.getBean(BusinessObjectService.class).save(apDocument); 555 556 // do GL entries for document creation 557 accountsPayableDocumentSpecificService.generateGLEntriesCreateAccountsPayableDocument(apDocument); 558 559 } 560 561 /** 562 * @see org.kuali.kfs.module.purap.document.service.AccountsPayableService#updateItemList(org.kuali.kfs.module.purap.document.AccountsPayableDocument) 563 */ 564 public void updateItemList(AccountsPayableDocument apDocument) { 565 // don't run the following if past full entry 566 if (purapService.isFullDocumentEntryCompleted(apDocument)) { 567 return; 568 } 569 if (apDocument instanceof VendorCreditMemoDocument) { 570 VendorCreditMemoDocument cm = (VendorCreditMemoDocument) apDocument; 571 if (cm.isSourceDocumentPaymentRequest()) { 572 // just update encumberances, items shouldn't change, get to them through po (or through preq) 573 List<PaymentRequestItem> items = cm.getPaymentRequestDocument().getItems(); 574 for (PaymentRequestItem preqItem : items) { 575 // skip inactive and below the line 576 if (preqItem.getItemType().isAdditionalChargeIndicator()) { 577 continue; 578 } 579 PurchaseOrderItem poItem = preqItem.getPurchaseOrderItem(); 580 CreditMemoItem cmItem = (CreditMemoItem) cm.getAPItemFromPOItem(poItem); 581 // take invoiced quantities from the lower of the preq and po if different 582 updateEncumberances(preqItem, poItem, cmItem); 583 } 584 } 585 else if (cm.isSourceDocumentPurchaseOrder()) { 586 PurchaseOrderDocument po = purchaseOrderService.getCurrentPurchaseOrder(apDocument.getPurchaseOrderIdentifier()); 587 List<PurchaseOrderItem> poItems = po.getItems(); 588 List<CreditMemoItem> cmItems = cm.getItems(); 589 // iterate through the above the line poItems to find matching 590 for (PurchaseOrderItem purchaseOrderItem : poItems) { 591 // skip inactive and below the line 592 if (purchaseOrderItem.getItemType().isAdditionalChargeIndicator()) { 593 continue; 594 } 595 596 CreditMemoItem cmItem = (CreditMemoItem) cm.getAPItemFromPOItem(purchaseOrderItem); 597 // check if any action needs to be taken on the items (i.e. add for new eligible items or remove for ineligible) 598 if (apDocument.getDocumentSpecificService().poItemEligibleForAp(apDocument, purchaseOrderItem)) { 599 // if eligible and not there - add 600 if (ObjectUtils.isNull(cmItem)) { 601 CreditMemoItem cmi = new CreditMemoItem(cm, purchaseOrderItem); 602 cmi.setPurapDocument(apDocument); 603 cmItems.add(cmi); 604 } 605 else { 606 // is eligible and on doc, update encumberances 607 // (this is only qty and amount for now NOTE we should also update other key fields, like description 608 // etc in case ammendment modified a line 609 updateEncumberance(purchaseOrderItem, cmItem); 610 } 611 } 612 else { // if not eligible and there - remove 613 if (ObjectUtils.isNotNull(cmItem)) { 614 cmItems.remove(cmItem); 615 // don't update encumberance 616 continue; 617 } 618 } 619 620 } 621 } // else do nothing 622 return; 623 624 // finally update encumbrances 625 } 626 else if (apDocument instanceof PaymentRequestDocument) { 627 628 // get a fresh purchase order 629 PurchaseOrderDocument po = purchaseOrderService.getCurrentPurchaseOrder(apDocument.getPurchaseOrderIdentifier()); 630 PaymentRequestDocument preq = (PaymentRequestDocument) apDocument; 631 632 List<PurchaseOrderItem> poItems = po.getItems(); 633 List<PaymentRequestItem> preqItems = preq.getItems(); 634 // iterate through the above the line poItems to find matching 635 for (PurchaseOrderItem purchaseOrderItem : poItems) { 636 // skip below the line 637 if (purchaseOrderItem.getItemType().isAdditionalChargeIndicator()) { 638 continue; 639 } 640 PaymentRequestItem preqItem = (PaymentRequestItem) preq.getAPItemFromPOItem(purchaseOrderItem); 641 // check if any action needs to be taken on the items (i.e. add for new eligible items or remove for ineligible) 642 if (apDocument.getDocumentSpecificService().poItemEligibleForAp(apDocument, purchaseOrderItem)) { 643 // if eligible and not there - add 644 if (ObjectUtils.isNull(preqItem)) { 645 PaymentRequestItem pri = new PaymentRequestItem(purchaseOrderItem, preq); 646 pri.setPurapDocument(apDocument); 647 preqItems.add(pri); 648 } 649 else { 650 updatePossibleAmmendedFields(purchaseOrderItem, preqItem); 651 } 652 } 653 else { // if not eligible and there - remove 654 if (ObjectUtils.isNotNull(preqItem)) { 655 preqItems.remove(preqItem); 656 } 657 } 658 659 } 660 } 661 } 662 663 /** 664 * Updates fields that could've been changed on amendment. 665 * 666 * @param sourceItem The purchase order item from which we're getting the unit price, catalog number and description to be set in the destItem. 667 * @param destItem The payment request item to which we're setting the unit price, catalog number and description. 668 */ 669 protected void updatePossibleAmmendedFields(PurchaseOrderItem sourceItem, PaymentRequestItem destItem) { 670 destItem.setPurchaseOrderItemUnitPrice(sourceItem.getItemUnitPrice()); 671 destItem.setItemCatalogNumber(sourceItem.getItemCatalogNumber()); 672 destItem.setItemDescription(sourceItem.getItemDescription()); 673 } 674 675 /** 676 * Updates encumberances. 677 * 678 * @param preqItem The payment request item from which we're obtaining the item quantity, unit price and extended price. 679 * @param poItem The purchase order item from which we're obtaining the invoice total quantity, unit price and invoice total amount. 680 * @param cmItem The credit memo item whose invoice total quantity, unit price and extended price are to be updated. 681 */ 682 protected void updateEncumberances(PaymentRequestItem preqItem, PurchaseOrderItem poItem, CreditMemoItem cmItem) { 683 if (poItem.getItemInvoicedTotalQuantity() != null && preqItem.getItemQuantity() != null && poItem.getItemInvoicedTotalQuantity().isLessThan(preqItem.getItemQuantity())) { 684 cmItem.setPreqInvoicedTotalQuantity(poItem.getItemInvoicedTotalQuantity()); 685 cmItem.setPreqUnitPrice(poItem.getItemUnitPrice()); 686 cmItem.setPreqTotalAmount(poItem.getItemInvoicedTotalAmount()); 687 } 688 else { 689 cmItem.setPreqInvoicedTotalQuantity(preqItem.getItemQuantity()); 690 cmItem.setPreqUnitPrice(preqItem.getItemUnitPrice()); 691 cmItem.setPreqTotalAmount(preqItem.getTotalAmount()); 692 } 693 } 694 695 /** 696 * Updates the encumberance related fields. 697 * 698 * @param purchaseOrderItem The purchase order item from which we're obtaining the invoice total quantity, unit price and invoice total amount. 699 * @param cmItem The credit memo item whose invoice total quantity, unit price and extended price are to be updated. 700 */ 701 protected void updateEncumberance(PurchaseOrderItem purchaseOrderItem, CreditMemoItem cmItem) { 702 cmItem.setPoInvoicedTotalQuantity(purchaseOrderItem.getItemInvoicedTotalQuantity()); 703 cmItem.setPreqUnitPrice(purchaseOrderItem.getItemUnitPrice()); 704 cmItem.setPoTotalAmount(purchaseOrderItem.getItemInvoicedTotalAmount()); 705 } 706 707 /** 708 * @see org.kuali.kfs.module.purap.document.service.AccountsPayableService#purchaseOrderItemEligibleForPayment(org.kuali.kfs.module.purap.businessobject.PurchaseOrderItem) 709 */ 710 public boolean purchaseOrderItemEligibleForPayment(PurchaseOrderItem poi) { 711 if (ObjectUtils.isNull(poi)) { 712 throw new RuntimeException("item null in purchaseOrderItemEligibleForPayment ... this should never happen"); 713 } 714 715 // if the po item is not active... skip it 716 if (!poi.isItemActiveIndicator()) { 717 return false; 718 } 719 720 ItemType poiType = poi.getItemType(); 721 722 if (poiType.isQuantityBasedGeneralLedgerIndicator()) { 723 if (poi.getItemQuantity().isGreaterThan(poi.getItemInvoicedTotalQuantity())) { 724 return true; 725 } 726 return false; 727 } 728 else { // not quantity based 729 if (poi.getItemOutstandingEncumberedAmount().isGreaterThan(KualiDecimal.ZERO)) { 730 return true; 731 } 732 return false; 733 } 734 } 735 736 } 737