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