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