View Javadoc
1   /*
2    * Copyright 2007 The Kuali Foundation
3    * 
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    * http://www.opensource.org/licenses/ecl2.php
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.ole.module.purap.document.service.impl;
17  
18  import org.kuali.ole.coa.businessobject.Account;
19  import org.kuali.ole.coa.service.AccountService;
20  import org.kuali.ole.gl.batch.ScrubberStep;
21  import org.kuali.ole.module.purap.PurapConstants;
22  import org.kuali.ole.module.purap.PurapConstants.PaymentRequestStatuses;
23  import org.kuali.ole.module.purap.PurapKeyConstants;
24  import org.kuali.ole.module.purap.PurapParameterConstants;
25  import org.kuali.ole.module.purap.businessobject.*;
26  import org.kuali.ole.module.purap.document.*;
27  import org.kuali.ole.module.purap.document.service.AccountsPayableDocumentSpecificService;
28  import org.kuali.ole.module.purap.document.service.AccountsPayableService;
29  import org.kuali.ole.module.purap.document.service.PurapService;
30  import org.kuali.ole.module.purap.document.service.PurchaseOrderService;
31  import org.kuali.ole.module.purap.service.PurapAccountingService;
32  import org.kuali.ole.module.purap.service.PurapGeneralLedgerService;
33  import org.kuali.ole.module.purap.util.ExpiredOrClosedAccount;
34  import org.kuali.ole.module.purap.util.ExpiredOrClosedAccountEntry;
35  import org.kuali.ole.sys.OLEConstants;
36  import org.kuali.ole.sys.businessobject.SourceAccountingLine;
37  import org.kuali.ole.sys.context.SpringContext;
38  import org.kuali.ole.sys.service.impl.OleParameterConstants;
39  import org.kuali.rice.core.api.datetime.DateTimeService;
40  import org.kuali.rice.core.api.util.type.KualiDecimal;
41  import org.kuali.rice.coreservice.framework.parameter.ParameterService;
42  import org.kuali.rice.kew.api.KewApiServiceLocator;
43  import org.kuali.rice.kew.api.WorkflowDocument;
44  import org.kuali.rice.kew.api.action.DocumentActionParameters;
45  import org.kuali.rice.kim.api.identity.Person;
46  import org.kuali.rice.kim.api.identity.PersonService;
47  import org.kuali.rice.kns.util.KNSGlobalVariables;
48  import org.kuali.rice.krad.UserSession;
49  import org.kuali.rice.krad.bo.Note;
50  import org.kuali.rice.krad.service.BusinessObjectService;
51  import org.kuali.rice.krad.service.DocumentService;
52  import org.kuali.rice.krad.service.NoteService;
53  import org.kuali.rice.krad.util.GlobalVariables;
54  import org.kuali.rice.krad.util.ObjectUtils;
55  import org.kuali.rice.krad.workflow.service.WorkflowDocumentService;
56  import org.springframework.transaction.annotation.Transactional;
57  
58  import java.util.*;
59  
60  
61  @Transactional
62  public class AccountsPayableServiceImpl implements AccountsPayableService {
63  
64      protected PurapAccountingService purapAccountingService;
65      protected PurapGeneralLedgerService purapGeneralLedgerService;
66      protected DocumentService documentService;
67      protected PurapService purapService;
68      protected ParameterService parameterService;
69      protected DateTimeService dateTimeService;
70      protected PurchaseOrderService purchaseOrderService;
71      protected AccountService accountService;
72  
73      public void setParameterService(ParameterService parameterService) {
74          this.parameterService = parameterService;
75      }
76  
77      public void setPurapService(PurapService purapService) {
78          this.purapService = purapService;
79      }
80  
81      public void setPurapAccountingService(PurapAccountingService purapAccountingService) {
82          this.purapAccountingService = purapAccountingService;
83      }
84  
85      public void setPurapGeneralLedgerService(PurapGeneralLedgerService purapGeneralLedgerService) {
86          this.purapGeneralLedgerService = purapGeneralLedgerService;
87      }
88  
89      public void setDocumentService(DocumentService documentService) {
90          this.documentService = documentService;
91      }
92  
93      public void setDateTimeService(DateTimeService dateTimeService) {
94          this.dateTimeService = dateTimeService;
95      }
96  
97      public void setPurchaseOrderService(PurchaseOrderService purchaseOrderService) {
98          this.purchaseOrderService = purchaseOrderService;
99      }
100 
101     public void setAccountService(AccountService accountService) {
102         this.accountService = accountService;
103     }
104 
105     /**
106      * @see org.kuali.ole.module.purap.document.service.AccountsPayableService#getExpiredOrClosedAccountList(org.kuali.ole.module.purap.document.AccountsPayableDocument)
107      */
108     @Override
109     public HashMap<String, ExpiredOrClosedAccountEntry> getExpiredOrClosedAccountList(AccountsPayableDocument document) {
110 
111         // Retrieve a list of accounts and replacement accounts, where accounts or closed or expired.
112         HashMap<String, ExpiredOrClosedAccountEntry> expiredOrClosedAccounts = expiredOrClosedAccountsList(document);
113 
114         return expiredOrClosedAccounts;
115     }
116 
117     /**
118      * @see org.kuali.ole.module.purap.document.service.AccountsPayableService#generateExpiredOrClosedAccountNote(org.kuali.ole.module.purap.document.AccountsPayableDocument,
119      *      java.util.HashMap)
120      */
121     @Override
122     public void generateExpiredOrClosedAccountNote(AccountsPayableDocument document, HashMap<String, ExpiredOrClosedAccountEntry> expiredOrClosedAccountList) {
123 
124         // create a note of all the replacement accounts
125         if (ObjectUtils.isNotNull(expiredOrClosedAccountList) && !expiredOrClosedAccountList.isEmpty()) {
126             addContinuationAccountsNote(document, expiredOrClosedAccountList);
127         }
128 
129     }
130 
131     /**
132      * @see org.kuali.ole.module.purap.document.service.AccountsPayableService#generateExpiredOrClosedAccountWarning(org.kuali.ole.module.purap.document.AccountsPayableDocument)
133      */
134     @Override
135     public void generateExpiredOrClosedAccountWarning(AccountsPayableDocument document) {
136 
137         // get user
138         Person user = GlobalVariables.getUserSession().getPerson();
139 
140         // get parameter to see if fiscal officers may see the continuation account warning
141         String showContinuationAccountWaringFO = parameterService.getParameterValueAsString(OleParameterConstants.PURCHASING_DOCUMENT.class, PurapConstants.PURAP_AP_SHOW_CONTINUATION_ACCOUNT_WARNING_FISCAL_OFFICERS);
142 
143         // get parameter to see if ap users may see the continuation account warning
144         String showContinuationAccountWaringAP = parameterService.getParameterValueAsString(OleParameterConstants.PURCHASING_DOCUMENT.class, PurapConstants.PURAP_AP_SHOW_CONTINUATION_ACCOUNT_WARNING_AP_USERS);
145 
146         // versus doing it in their respective documents (preq, credit memo)
147         // document is past full entry and
148         // user is a fiscal officer and a system parameter is set to allow viewing
149         // and if the continuation account indicator is set
150         if (isFiscalUser(document, user) && "Y".equals(showContinuationAccountWaringFO) && (document.isContinuationAccountIndicator())) {
151             KNSGlobalVariables.getMessageList().add(PurapKeyConstants.MESSAGE_CLOSED_OR_EXPIRED_ACCOUNTS_REPLACED);
152         }
153 
154         // document is past full entry and
155         // user is an AP User and a system parameter is set to allow viewing
156         // and if the continuation account indicator is set
157         if (isAPUser(document, user) && "Y".equals(showContinuationAccountWaringAP) && (document.isContinuationAccountIndicator())) {
158             KNSGlobalVariables.getMessageList().add(PurapKeyConstants.MESSAGE_CLOSED_OR_EXPIRED_ACCOUNTS_REPLACED);
159         }
160 
161     }
162 
163     /**
164      * @see org.kuali.ole.module.purap.document.service.AccountsPayableService#processExpiredOrClosedAccount(org.kuali.ole.module.purap.businessobject.PurApAccountingLineBase,
165      *      java.util.HashMap)
166      */
167     @Override
168     public void processExpiredOrClosedAccount(PurApAccountingLineBase acctLineBase, HashMap<String, ExpiredOrClosedAccountEntry> expiredOrClosedAccountList) {
169 
170         ExpiredOrClosedAccountEntry accountEntry = null;
171         String acctKey = acctLineBase.getChartOfAccountsCode() + "-" + acctLineBase.getAccountNumber();
172 
173         if (expiredOrClosedAccountList.containsKey(acctKey)) {
174 
175             accountEntry = expiredOrClosedAccountList.get(acctKey);
176 
177             if (accountEntry.getOriginalAccount().isContinuationAccountMissing() == false) {
178                 acctLineBase.setChartOfAccountsCode(accountEntry.getReplacementAccount().getChartOfAccountsCode());
179                 acctLineBase.setAccountNumber(accountEntry.getReplacementAccount().getAccountNumber());
180                 acctLineBase.refreshReferenceObject("chart");
181                 acctLineBase.refreshReferenceObject("account");
182             }
183         }
184     }
185 
186     /**
187      * Creates and adds a note indicating accounts replaced and what they replaced and attaches it to the document.
188      *
189      * @param document The accounts payable document to which we're adding the note.
190      * @param accounts The HashMap where the keys are the string representations of the chart and account of the
191      *                 original account and the values are the ExpiredOrClosedAccountEntry.
192      */
193     protected void addContinuationAccountsNote(AccountsPayableDocument document, HashMap<String, ExpiredOrClosedAccountEntry> accounts) {
194         String noteText;
195         StringBuffer sb = new StringBuffer("");
196         ExpiredOrClosedAccountEntry accountEntry = null;
197         ExpiredOrClosedAccount originalAccount = null;
198         ExpiredOrClosedAccount replacementAccount = null;
199 
200         // List the entries using entrySet()
201         Set entries = accounts.entrySet();
202         Iterator it = entries.iterator();
203 
204         // loop through the accounts found to be expired/closed and add if they have a continuation account
205         while (it.hasNext()) {
206             Map.Entry entry = (Map.Entry) it.next();
207             accountEntry = (ExpiredOrClosedAccountEntry) entry.getValue();
208             originalAccount = accountEntry.getOriginalAccount();
209             replacementAccount = accountEntry.getReplacementAccount();
210 
211             // only print out accounts that were replaced and not missing a continuation account
212             if (originalAccount.isContinuationAccountMissing() == false) {
213                 sb.append(" Account " + originalAccount.getAccountString() + " was replaced with account " + replacementAccount.getAccountString() + " ; ");
214             }
215 
216         }
217 
218         // if a note was created, add it to the document
219         if (sb.toString().length() > 0) {
220             try {
221                 Note resetNote = documentService.createNoteFromDocument(document, sb.toString());
222                 document.addNote(resetNote);
223             } catch (Exception e) {
224                 throw new RuntimeException(PurapConstants.REQ_UNABLE_TO_CREATE_NOTE + " " + e);
225             }
226         }
227     }
228 
229     /**
230      * Gets the replacement account for the specified closed account.
231      * In this case it's the continuation account of the the specified account.
232      *
233      * @param account the specified account which is closed.
234      * @return the replacement account for the specified account.
235      * @document the document the account is associated with.
236      */
237     protected Account getReplaceAccountForClosedAccount(Account account, AccountsPayableDocument document) {
238         if (account == null) {
239             return null; // this should never happen
240         }
241         Account continueAccount = accountService.getByPrimaryId(account.getContinuationFinChrtOfAcctCd(), account.getContinuationAccountNumber());
242         return continueAccount;
243     }
244 
245     /**
246      * Gets the replacement account for the specified expired account.
247      * In this case it's the continuation account of the the specified account.
248      *
249      * @param account the specified account which is expired.
250      * @return the replacement account for the specified account.
251      * @document the document the account is associated with.
252      */
253     protected Account getReplaceAccountForExpiredAccount(Account account, AccountsPayableDocument document) {
254         if (account == null) {
255             return null; // this should never happen
256         }
257         Account continueAccount = accountService.getByPrimaryId(account.getContinuationFinChrtOfAcctCd(), account.getContinuationAccountNumber());
258         return continueAccount;
259     }
260 
261     /**
262      * Generates a list of replacement accounts for expired or closed accounts, as well as for expired/closed accounts without a continuation account.
263      *
264      * @param document The accounts payable document from which we're obtaining the purchase order id to be used
265      *                 to obtain the purchase order document, whose accounts we'll use to generate the list of
266      *                 replacement accounts for expired or closed accounts.
267      * @return The HashMap where the keys are the string representations of the chart and account
268      *         of the original account and the values are the ExpiredOrClosedAccountEntry.
269      */
270     protected HashMap<String, ExpiredOrClosedAccountEntry> expiredOrClosedAccountsList(AccountsPayableDocument document) {
271         // retrieve po from apdoc
272         PurchaseOrderDocument po = document.getPurchaseOrderDocument();
273         if (po == null && document instanceof VendorCreditMemoDocument) {
274             PaymentRequestDocument preq = ((VendorCreditMemoDocument) document).getPaymentRequestDocument();
275             if (preq == null) {
276                 return null; // this should never happen
277             }
278             po = ((VendorCreditMemoDocument) document).getPaymentRequestDocument().getPurchaseOrderDocument();
279         }
280         if (po == null) {
281             return null; // this should never happen
282         }
283 
284         // initialize
285         List<SourceAccountingLine> acctLines = purapAccountingService.generateSummary(po.getItemsActiveOnly());
286         HashMap<String, ExpiredOrClosedAccountEntry> eocAcctMap = new HashMap<String, ExpiredOrClosedAccountEntry>();
287 
288         // loop through accounting lines
289         for (SourceAccountingLine acctLine : acctLines) {
290             Account account = accountService.getByPrimaryId(acctLine.getChartOfAccountsCode(), acctLine.getAccountNumber());
291             Account repAccount = null;
292             boolean replace = false;
293 
294             // 1. if the account is closed, get the continuation account as replacement
295             if (!account.isActive()) {
296                 repAccount = getReplaceAccountForClosedAccount(account, document);
297                 replace = true;
298             }
299             // 2. if the account is C&G and is expired for more than 90 days, get the continuation account as replacement
300             else if (account.isExpired()) {
301                 // retrieve extension limit (grace period)
302                 String expirationExtensionDays = parameterService.getParameterValueAsString(ScrubberStep.class, OLEConstants.SystemGroupParameterNames.GL_SCRUBBER_VALIDATION_DAYS_OFFSET);
303                 int expirationExtensionDaysInt = 90; // default to 90 days (approximately 3 months)
304                 if (expirationExtensionDays.trim().length() > 0) {
305                     expirationExtensionDaysInt = new Integer(expirationExtensionDays).intValue();
306                 }
307                 // if account is C&G and expired beyond grace period then get replacement
308                 if ((account.isForContractsAndGrants() && dateTimeService.dateDiff(account.getAccountExpirationDate(), dateTimeService.getCurrentDate(), true) > expirationExtensionDaysInt)) {
309                     repAccount = getReplaceAccountForExpiredAccount(account, document);
310                     replace = true;
311                 }
312                 // otherwise if the account is not C&G, or it's expired within the grace period, do nothing         
313             }
314 
315             // if replacement needed, set up ExpiredOrClosedAccount entry and add it to the eocAcctMap
316             if (replace) {
317                 ExpiredOrClosedAccountEntry eocAcctEntry = new ExpiredOrClosedAccountEntry();
318                 ExpiredOrClosedAccount originAcct = new ExpiredOrClosedAccount(acctLine.getChartOfAccountsCode(), acctLine.getAccountNumber(), acctLine.getSubAccountNumber());
319                 ExpiredOrClosedAccount replaceAcct = null;
320                 if (repAccount == null) {
321                     replaceAcct = new ExpiredOrClosedAccount();
322                     originAcct.setContinuationAccountMissing(true);
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     @Override
344     public HashMap<String, ExpiredOrClosedAccountEntry> expiredOrClosedAccountsList(PurchaseOrderDocument po) {
345         HashMap<String, ExpiredOrClosedAccountEntry> list = new HashMap<String, ExpiredOrClosedAccountEntry>();
346         ExpiredOrClosedAccountEntry entry = null;
347         ExpiredOrClosedAccount originalAcct = null;
348         ExpiredOrClosedAccount replaceAcct = null;
349         String chartAccount = null;
350 
351         if (po != null) {
352             // get list of active accounts
353             List<SourceAccountingLine> accountList = purapAccountingService.generateSummary(po.getItemsActiveOnly());
354 
355             // loop through accounts
356             for (SourceAccountingLine poAccountingLine : accountList) {
357                 Account account = accountService.getByPrimaryId(poAccountingLine.getChartOfAccountsCode(), poAccountingLine.getAccountNumber());
358                 entry = new ExpiredOrClosedAccountEntry();
359                 originalAcct = new ExpiredOrClosedAccount(poAccountingLine.getChartOfAccountsCode(), poAccountingLine.getAccountNumber(), poAccountingLine.getSubAccountNumber());
360 
361                 if (!account.isActive()) {
362                     // 1. if the account is closed, get the continuation account and add it to the list
363                     Account continuationAccount = accountService.getByPrimaryId(account.getContinuationFinChrtOfAcctCd(), account.getContinuationAccountNumber());
364 
365                     if (continuationAccount == null) {
366                         replaceAcct = new ExpiredOrClosedAccount();
367                         originalAcct.setContinuationAccountMissing(true);
368 
369                         entry.setOriginalAccount(originalAcct);
370                         entry.setReplacementAccount(replaceAcct);
371 
372                         list.put(createChartAccountString(originalAcct), entry);
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                 } else if (account.isExpired()) {
385                     Account continuationAccount = accountService.getByPrimaryId(account.getContinuationFinChrtOfAcctCd(), account.getContinuationAccountNumber());
386                     String expirationExtensionDays = parameterService.getParameterValueAsString(ScrubberStep.class, OLEConstants.SystemGroupParameterNames.GL_SCRUBBER_VALIDATION_DAYS_OFFSET);
387                     int expirationExtensionDaysInt = 3 * 30; // default to 90 days (approximately 3 months)
388 
389                     if (expirationExtensionDays.trim().length() > 0) {
390 
391                         expirationExtensionDaysInt = new Integer(expirationExtensionDays).intValue();
392                     }
393 
394                     // if account is C&G and expired then add to list.
395                     if ((account.isForContractsAndGrants() && dateTimeService.dateDiff(account.getAccountExpirationDate(), dateTimeService.getCurrentDate(), true) > expirationExtensionDaysInt)) {
396 
397                         if (continuationAccount == null) {
398                             replaceAcct = new ExpiredOrClosedAccount();
399                             originalAcct.setContinuationAccountMissing(true);
400 
401                             entry.setOriginalAccount(originalAcct);
402                             entry.setReplacementAccount(replaceAcct);
403 
404                             list.put(createChartAccountString(originalAcct), entry);
405                         } else {
406                             replaceAcct = new ExpiredOrClosedAccount(continuationAccount.getChartOfAccountsCode(), continuationAccount.getAccountNumber(), poAccountingLine.getSubAccountNumber());
407 
408                             entry.setOriginalAccount(originalAcct);
409                             entry.setReplacementAccount(replaceAcct);
410 
411                             list.put(createChartAccountString(originalAcct), entry);
412                         }
413                     }
414 
415                     // if account is not C&G, use the same account, do not replace
416                 }
417             }
418         }
419         return list;
420     }
421 
422     /**
423      * Creates a chart-account string.
424      *
425      * @param ecAccount The account whose chart and account number we're going to use to create the resulting String for this method.
426      * @return The string representing the chart and account number of the given ecAccount.
427      */
428     protected String createChartAccountString(ExpiredOrClosedAccount ecAccount) {
429         StringBuffer buff = new StringBuffer("");
430 
431         buff.append(ecAccount.getChartOfAccountsCode());
432         buff.append("-");
433         buff.append(ecAccount.getAccountNumber());
434 
435         return buff.toString();
436     }
437 
438     /**
439      * Determines if the user is a fiscal officer.  Currently this only checks the doc and workflow status for approval requested
440      *
441      * @param document The document to be used to check the status code and whether the workflow approval is requested.
442      * @param user     The current user.
443      * @return boolean true if the user is a fiscal officer.
444      */
445     protected boolean isFiscalUser(AccountsPayableDocument document, Person user) {
446         boolean isFiscalUser = false;
447 
448         if (PaymentRequestStatuses.APPDOC_AWAITING_FISCAL_REVIEW.equals(document.getApplicationDocumentStatus()) && document.getDocumentHeader().getWorkflowDocument().isApprovalRequested()) {
449             isFiscalUser = true;
450         }
451 
452         return isFiscalUser;
453     }
454 
455     /**
456      * Determines if the user is an AP user.  Currently this only checks the doc and workflow status for approval requested
457      *
458      * @param document The document to be used to check the status code and whether the workflow approval is requested.
459      * @param user     The current user.
460      * @return boolean true if the user is an AP User.
461      */
462     protected boolean isAPUser(AccountsPayableDocument document, Person user) {
463         boolean isFiscalUser = false;
464 
465         if ((PaymentRequestStatuses.APPDOC_AWAITING_ACCOUNTS_PAYABLE_REVIEW.equals(document.getApplicationDocumentStatus()) &&
466                 document.getDocumentHeader().getWorkflowDocument().isApprovalRequested()) ||
467                 PaymentRequestStatuses.APPDOC_IN_PROCESS.equals(document.getApplicationDocumentStatus())) {
468             isFiscalUser = true;
469         }
470 
471         return isFiscalUser;
472     }
473 
474     /**
475      * @see org.kuali.ole.module.purap.document.service.AccountsPayableService#cancelAccountsPayableDocument(org.kuali.ole.module.purap.document.AccountsPayableDocument, java.lang.String)
476      */
477     @Override
478     public void cancelAccountsPayableDocument(AccountsPayableDocument apDocument, String currentNodeName) {
479         if (purapService.isFullDocumentEntryCompleted(apDocument)) {
480             purapGeneralLedgerService.generateEntriesCancelAccountsPayableDocument(apDocument);
481         }
482         AccountsPayableDocumentSpecificService accountsPayableDocumentSpecificService = apDocument.getDocumentSpecificService();
483         accountsPayableDocumentSpecificService.updateStatusByNode(currentNodeName, apDocument);
484 
485         // close/reopen purchase order.
486         accountsPayableDocumentSpecificService.takePurchaseOrderCancelAction(apDocument);
487     }
488 
489     /**
490      * @see org.kuali.ole.module.purap.document.service.AccountsPayableService#cancelAccountsPayableDocumentByCheckingDocumentStatus(org.kuali.ole.module.purap.document.AccountsPayableDocument, java.lang.String)
491      */
492     @Override
493     public void cancelAccountsPayableDocumentByCheckingDocumentStatus(AccountsPayableDocument document, String noteText) throws Exception {
494         DocumentService documentService = SpringContext.getBean(DocumentService.class);
495 
496         if (PurapConstants.CreditMemoStatuses.APPDOC_IN_PROCESS.equals(document.getApplicationDocumentStatus())) {
497             //prior to submit, just call regular cancel logic
498             documentService.cancelDocument(document, noteText);
499         } else if (PurapConstants.CreditMemoStatuses.APPDOC_AWAITING_ACCOUNTS_PAYABLE_REVIEW.equals(document.getApplicationDocumentStatus())) {
500             //while awaiting AP approval, just call regular disapprove logic as user will have action request
501             documentService.disapproveDocument(document, noteText);
502         } else if (document instanceof PaymentRequestDocument && PurapConstants.PaymentRequestStatuses.APPDOC_AWAITING_FISCAL_REVIEW.equals(document.getApplicationDocumentStatus()) && ((PaymentRequestDocument) document).isPaymentRequestedCancelIndicator()) {
503             // special logic to disapprove PREQ as the fiscal officer
504             DocumentActionParameters.Builder p = DocumentActionParameters.Builder.create(document.getDocumentNumber(), document.getLastActionPerformedByPersonId());
505             p.setAnnotation("Document cancelled after requested cancel by " + GlobalVariables.getUserSession().getPrincipalName());
506             KewApiServiceLocator.getWorkflowDocumentActionsService().disapprove(p.build());
507         } else {
508             UserSession originalUserSession = GlobalVariables.getUserSession();
509             WorkflowDocument originalWorkflowDocument = document.getDocumentHeader().getWorkflowDocument();
510             //any other time, perform special logic to cancel the document
511             if (!document.getDocumentHeader().getWorkflowDocument().isApproved()) {
512                 try {
513                     // person canceling may not have an action requested on the document
514                     Person userRequestedCancel = SpringContext.getBean(PersonService.class).getPerson(document.getLastActionPerformedByPersonId());
515                     GlobalVariables.setUserSession(new UserSession(OLEConstants.SYSTEM_USER));
516 
517                     WorkflowDocumentService workflowDocumentService = SpringContext.getBean(WorkflowDocumentService.class);
518                     WorkflowDocument newWorkflowDocument = workflowDocumentService.loadWorkflowDocument(document.getDocumentNumber(), GlobalVariables.getUserSession().getPerson());
519                     document.getDocumentHeader().setWorkflowDocument(newWorkflowDocument);
520 
521                     String annotation = "Document Cancelled by user " + originalUserSession.getPerson().getName() + " (" + originalUserSession.getPerson().getPrincipalName() + ")";
522                     if (ObjectUtils.isNotNull(userRequestedCancel)) {
523                         annotation.concat(" per request of user " + userRequestedCancel.getName() + " (" + userRequestedCancel.getPrincipalName() + ")");
524                     }
525                     documentService.superUserDisapproveDocument(document, annotation);
526                 } finally {
527                     GlobalVariables.setUserSession(originalUserSession);
528                     document.getDocumentHeader().setWorkflowDocument(originalWorkflowDocument);
529                 }
530             } else {
531                 // call gl method here (no reason for post processing since workflow done)
532                 SpringContext.getBean(AccountsPayableService.class).cancelAccountsPayableDocument(document, "");
533                 document.getDocumentHeader().getWorkflowDocument().logAnnotation("Document Cancelled by user " + originalUserSession.getPerson().getName() + " (" + originalUserSession.getPerson().getPrincipalName() + ")");
534             }
535         }
536 
537         Note noteObj = documentService.createNoteFromDocument(document, noteText);
538         document.addNote(noteObj);
539         SpringContext.getBean(NoteService.class).save(noteObj);
540     }
541 
542     /**
543      * @see org.kuali.ole.module.purap.document.service.AccountsPayableService#performLogicForFullEntryCompleted(org.kuali.ole.module.purap.document.PurchasingAccountsPayableDocument)
544      */
545     @Override
546     public void performLogicForFullEntryCompleted(PurchasingAccountsPayableDocument purapDocument) {
547         AccountsPayableDocument apDocument = (AccountsPayableDocument) purapDocument;
548         AccountsPayableDocumentSpecificService accountsPayableDocumentSpecificService = apDocument.getDocumentSpecificService();
549 
550         // eliminate unentered items
551         purapService.deleteUnenteredItems(apDocument);
552 
553         // change accounts from percents to dollars
554         purapAccountingService.updateAccountAmounts(apDocument);
555 
556         //set the AP approval date always when the GL entries are created (treated more of an AP processed date)
557         apDocument.setAccountsPayableApprovalTimestamp(dateTimeService.getCurrentTimestamp());
558 
559         // save for persistence
560         SpringContext.getBean(BusinessObjectService.class).save(apDocument);
561 
562         // do GL entries for document creation
563         accountsPayableDocumentSpecificService.generateGLEntriesCreateAccountsPayableDocument(apDocument);
564 
565     }
566 
567     /**
568      * @see org.kuali.ole.module.purap.document.service.AccountsPayableService#updateItemList(org.kuali.ole.module.purap.document.AccountsPayableDocument)
569      */
570     @Override
571     public void updateItemList(AccountsPayableDocument apDocument) {
572         // don't run the following if past full entry
573         if (purapService.isFullDocumentEntryCompleted(apDocument)) {
574             return;
575         }
576         if (apDocument instanceof VendorCreditMemoDocument) {
577             VendorCreditMemoDocument cm = (VendorCreditMemoDocument) apDocument;
578             if (cm.isSourceDocumentPaymentRequest()) {
579                 // just update encumberances, items shouldn't change, get to them through po (or through preq)
580                 List<PaymentRequestItem> items = cm.getPaymentRequestDocument().getItems();
581                 for (PaymentRequestItem preqItem : items) {
582                     // skip inactive and below the line
583                     if (preqItem.getItemType().isAdditionalChargeIndicator()) {
584                         continue;
585                     }
586                     PurchaseOrderItem poItem = preqItem.getPurchaseOrderItem();
587                     CreditMemoItem cmItem = (CreditMemoItem) cm.getAPItemFromPOItem(poItem);
588                     // take invoiced quantities from the lower of the preq and po if different
589                     updateEncumberances(preqItem, poItem, cmItem);
590                 }
591             } else if (cm.isSourceDocumentPurchaseOrder()) {
592                 PurchaseOrderDocument po = purchaseOrderService.getCurrentPurchaseOrder(apDocument.getPurchaseOrderIdentifier());
593                 List<PurchaseOrderItem> poItems = po.getItems();
594                 List<CreditMemoItem> cmItems = cm.getItems();
595                 // iterate through the above the line poItems to find matching
596                 for (PurchaseOrderItem purchaseOrderItem : poItems) {
597                     // skip inactive and below the line
598                     if (purchaseOrderItem.getItemType().isAdditionalChargeIndicator()) {
599                         continue;
600                     }
601 
602                     CreditMemoItem cmItem = (CreditMemoItem) cm.getAPItemFromPOItem(purchaseOrderItem);
603                     // check if any action needs to be taken on the items (i.e. add for new eligible items or remove for ineligible)
604                     if (apDocument.getDocumentSpecificService().poItemEligibleForAp(apDocument, purchaseOrderItem)) {
605                         // if eligible and not there - add
606                         if (ObjectUtils.isNull(cmItem)) {
607                             CreditMemoItem cmi = new CreditMemoItem(cm, purchaseOrderItem);
608                             cmi.setPurapDocument(apDocument);
609                             cmItems.add(cmi);
610                         } else {
611                             // is eligible and on doc, update encumberances
612                             // (this is only qty and amount for now NOTE we should also update other key fields, like description
613                             // etc in case ammendment modified a line
614                             updateEncumberance(purchaseOrderItem, cmItem);
615                         }
616                     } else { // if not eligible and there - remove
617                         if (ObjectUtils.isNotNull(cmItem)) {
618                             cmItems.remove(cmItem);
619                             // don't update encumberance
620                             continue;
621                         }
622                     }
623 
624                 }
625             } // else do nothing
626             return;
627 
628             // finally update encumbrances
629         } else if (apDocument instanceof PaymentRequestDocument) {
630 
631             // get a fresh purchase order
632             PurchaseOrderDocument po = purchaseOrderService.getCurrentPurchaseOrder(apDocument.getPurchaseOrderIdentifier());
633             PaymentRequestDocument preq = (PaymentRequestDocument) apDocument;
634 
635             List<PurchaseOrderItem> poItems = po.getItems();
636             List<PaymentRequestItem> preqItems = preq.getItems();
637             // iterate through the above the line poItems to find matching
638             for (PurchaseOrderItem purchaseOrderItem : poItems) {
639                 // skip below the line
640                 if (purchaseOrderItem.getItemType().isAdditionalChargeIndicator()) {
641                     continue;
642                 }
643                 PaymentRequestItem preqItem = (PaymentRequestItem) preq.getAPItemFromPOItem(purchaseOrderItem);
644                 // check if any action needs to be taken on the items (i.e. add for new eligible items or remove for ineligible)
645                 if (apDocument.getDocumentSpecificService().poItemEligibleForAp(apDocument, purchaseOrderItem)) {
646                     // if eligible and not there - add
647                     if (ObjectUtils.isNull(preqItem)) {
648                         PaymentRequestItem pri = new PaymentRequestItem(purchaseOrderItem, preq);
649                         pri.setPurapDocument(apDocument);
650                         preqItems.add(pri);
651                     } else {
652                         updatePossibleAmmendedFields(purchaseOrderItem, preqItem);
653                     }
654                 } else { // if not eligible and there - remove
655                     if (ObjectUtils.isNotNull(preqItem)) {
656                         preqItems.remove(preqItem);
657                     }
658                 }
659 
660             }
661         }
662     }
663 
664     /**
665      * Updates fields that could've been changed on amendment.
666      *
667      * @param sourceItem The purchase order item from which we're getting the unit price, catalog number and description to be set in the destItem.
668      * @param destItem   The payment request item to which we're setting the unit price, catalog number and description.
669      */
670     protected void updatePossibleAmmendedFields(PurchaseOrderItem sourceItem, PaymentRequestItem destItem) {
671         destItem.setPurchaseOrderItemUnitPrice(sourceItem.getItemUnitPrice());
672         destItem.setItemCatalogNumber(sourceItem.getItemCatalogNumber());
673         destItem.setItemDescription(sourceItem.getItemDescription());
674     }
675 
676     /**
677      * Updates encumberances.
678      *
679      * @param preqItem The payment request item from which we're obtaining the item quantity, unit price and extended price.
680      * @param poItem   The purchase order item from which we're obtaining the invoice total quantity, unit price and invoice total amount.
681      * @param cmItem   The credit memo item whose invoice total quantity, unit price and extended price are to be updated.
682      */
683     protected void updateEncumberances(PaymentRequestItem preqItem, PurchaseOrderItem poItem, CreditMemoItem cmItem) {
684         if (poItem.getItemInvoicedTotalQuantity() != null && preqItem.getItemQuantity() != null && poItem.getItemInvoicedTotalQuantity().isLessThan(preqItem.getItemQuantity())) {
685             cmItem.setPreqInvoicedTotalQuantity(poItem.getItemInvoicedTotalQuantity());
686             cmItem.setPreqUnitPrice(poItem.getItemUnitPrice());
687             cmItem.setPreqTotalAmount(poItem.getItemInvoicedTotalAmount());
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.ole.module.purap.document.service.AccountsPayableService#purchaseOrderItemEligibleForPayment(org.kuali.ole.module.purap.businessobject.PurchaseOrderItem)
709      */
710     @Override
711     public boolean purchaseOrderItemEligibleForPayment(PurchaseOrderItem poi) {
712         if (ObjectUtils.isNull(poi)) {
713             throw new RuntimeException("item null in purchaseOrderItemEligibleForPayment ... this should never happen");
714         }
715 
716         // if the po item is not active... skip it
717         if (!poi.isItemActiveIndicator()) {
718             return false;
719         }
720 
721         ItemType poiType = poi.getItemType();
722 
723         if (poiType.isQuantityBasedGeneralLedgerIndicator()) {
724             if (poi.getItemQuantity().isGreaterThan(poi.getItemInvoicedTotalQuantity())) {
725                 return true;
726             }
727             return false;
728         } else { // not quantity based
729             if (poi.getItemOutstandingEncumberedAmount().isGreaterThan(KualiDecimal.ZERO)) {
730                 return true;
731             }
732             return false;
733         }
734     }
735 
736     /**
737      * @see org.kuali.ole.module.purap.document.service.AccountsPayableService#canCopyAccountingLinesWithZeroAmount()
738      */
739     @Override
740     public boolean canCopyAccountingLinesWithZeroAmount() {
741         boolean canCopyLine = false;
742 
743         // get parameter to see if accounting line with zero dollar amount can be copied from PO to PREQ.
744         String copyZeroAmountLine = parameterService.getParameterValueAsString(OleParameterConstants.PURCHASING_DOCUMENT.class, PurapParameterConstants.COPY_ACCOUNTING_LINES_WITH_ZERO_AMOUNT_FROM_PO_TO_PREQ_IND);
745 
746         if ("Y".equalsIgnoreCase(copyZeroAmountLine)) {
747             return true;
748         }
749 
750         return canCopyLine;
751     }
752 
753 }
754