/*
* The Kuali Financial System, a comprehensive financial management system for higher education.
*
* Copyright 2005-2014 The Kuali Foundation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.kuali.kfs.module.purap.document.service.impl;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.kuali.kfs.coa.businessobject.Account;
import org.kuali.kfs.coa.service.AccountService;
import org.kuali.kfs.gl.batch.ScrubberStep;
import org.kuali.kfs.module.purap.PurapConstants;
import org.kuali.kfs.module.purap.PurapConstants.PaymentRequestStatuses;
import org.kuali.kfs.module.purap.PurapKeyConstants;
import org.kuali.kfs.module.purap.PurapParameterConstants;
import org.kuali.kfs.module.purap.businessobject.CreditMemoItem;
import org.kuali.kfs.module.purap.businessobject.ItemType;
import org.kuali.kfs.module.purap.businessobject.PaymentRequestItem;
import org.kuali.kfs.module.purap.businessobject.PurApAccountingLineBase;
import org.kuali.kfs.module.purap.businessobject.PurchaseOrderItem;
import org.kuali.kfs.module.purap.document.AccountsPayableDocument;
import org.kuali.kfs.module.purap.document.PaymentRequestDocument;
import org.kuali.kfs.module.purap.document.PurchaseOrderDocument;
import org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument;
import org.kuali.kfs.module.purap.document.VendorCreditMemoDocument;
import org.kuali.kfs.module.purap.document.service.AccountsPayableDocumentSpecificService;
import org.kuali.kfs.module.purap.document.service.AccountsPayableService;
import org.kuali.kfs.module.purap.document.service.PurapService;
import org.kuali.kfs.module.purap.document.service.PurchaseOrderService;
import org.kuali.kfs.module.purap.service.PurapAccountingService;
import org.kuali.kfs.module.purap.service.PurapGeneralLedgerService;
import org.kuali.kfs.module.purap.util.ExpiredOrClosedAccount;
import org.kuali.kfs.module.purap.util.ExpiredOrClosedAccountEntry;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.businessobject.SourceAccountingLine;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.kfs.sys.service.impl.KfsParameterConstants;
import org.kuali.rice.core.api.config.property.ConfigurationService;
import org.kuali.rice.core.api.datetime.DateTimeService;
import org.kuali.rice.core.api.util.type.KualiDecimal;
import org.kuali.rice.coreservice.framework.parameter.ParameterService;
import org.kuali.rice.kew.api.KewApiServiceLocator;
import org.kuali.rice.kew.api.WorkflowDocument;
import org.kuali.rice.kew.api.action.DocumentActionParameters;
import org.kuali.rice.kim.api.identity.Person;
import org.kuali.rice.kim.api.identity.PersonService;
import org.kuali.rice.kim.api.identity.principal.Principal;
import org.kuali.rice.kim.api.services.KimApiServiceLocator;
import org.kuali.rice.kns.util.KNSGlobalVariables;
import org.kuali.rice.krad.UserSession;
import org.kuali.rice.krad.bo.Note;
import org.kuali.rice.krad.service.BusinessObjectService;
import org.kuali.rice.krad.service.DocumentService;
import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
import org.kuali.rice.krad.service.NoteService;
import org.kuali.rice.krad.util.GlobalVariables;
import org.kuali.rice.krad.util.ObjectUtils;
import org.kuali.rice.krad.workflow.service.WorkflowDocumentService;
import org.springframework.transaction.annotation.Transactional;
@Transactional
public class AccountsPayableServiceImpl implements AccountsPayableService {
protected PurapAccountingService purapAccountingService;
protected PurapGeneralLedgerService purapGeneralLedgerService;
protected DocumentService documentService;
protected PurapService purapService;
protected ParameterService parameterService;
protected DateTimeService dateTimeService;
protected PurchaseOrderService purchaseOrderService;
protected AccountService accountService;
public void setParameterService(ParameterService parameterService) {
this.parameterService = parameterService;
}
public void setPurapService(PurapService purapService) {
this.purapService = purapService;
}
public void setPurapAccountingService(PurapAccountingService purapAccountingService) {
this.purapAccountingService = purapAccountingService;
}
public void setPurapGeneralLedgerService(PurapGeneralLedgerService purapGeneralLedgerService) {
this.purapGeneralLedgerService = purapGeneralLedgerService;
}
public void setDocumentService(DocumentService documentService) {
this.documentService = documentService;
}
public void setDateTimeService(DateTimeService dateTimeService) {
this.dateTimeService = dateTimeService;
}
public void setPurchaseOrderService(PurchaseOrderService purchaseOrderService) {
this.purchaseOrderService = purchaseOrderService;
}
public void setAccountService(AccountService accountService) {
this.accountService = accountService;
}
/**
* @see org.kuali.kfs.module.purap.document.service.AccountsPayableService#getExpiredOrClosedAccountList(org.kuali.kfs.module.purap.document.AccountsPayableDocument)
*/
@Override
public HashMap<String, ExpiredOrClosedAccountEntry> getExpiredOrClosedAccountList(AccountsPayableDocument document) {
// Retrieve a list of accounts and replacement accounts, where accounts or closed or expired.
HashMap<String, ExpiredOrClosedAccountEntry> expiredOrClosedAccounts = expiredOrClosedAccountsList(document);
return expiredOrClosedAccounts;
}
/**
* @see org.kuali.kfs.module.purap.document.service.AccountsPayableService#generateExpiredOrClosedAccountNote(org.kuali.kfs.module.purap.document.AccountsPayableDocument,
* java.util.HashMap)
*/
@Override
public void generateExpiredOrClosedAccountNote(AccountsPayableDocument document, HashMap<String, ExpiredOrClosedAccountEntry> expiredOrClosedAccountList) {
// create a note of all the replacement accounts
if (ObjectUtils.isNotNull(expiredOrClosedAccountList) && !expiredOrClosedAccountList.isEmpty()) {
addContinuationAccountsNote(document, expiredOrClosedAccountList);
}
}
/**
* @see org.kuali.kfs.module.purap.document.service.AccountsPayableService#generateExpiredOrClosedAccountWarning(org.kuali.kfs.module.purap.document.AccountsPayableDocument)
*/
@Override
public void generateExpiredOrClosedAccountWarning(AccountsPayableDocument document) {
// get user
Person user = GlobalVariables.getUserSession().getPerson();
// get parameter to see if fiscal officers may see the continuation account warning
String showContinuationAccountWaringFO = parameterService.getParameterValueAsString(KfsParameterConstants.PURCHASING_DOCUMENT.class, PurapConstants.PURAP_AP_SHOW_CONTINUATION_ACCOUNT_WARNING_FISCAL_OFFICERS);
// get parameter to see if ap users may see the continuation account warning
String showContinuationAccountWaringAP = parameterService.getParameterValueAsString(KfsParameterConstants.PURCHASING_DOCUMENT.class, PurapConstants.PURAP_AP_SHOW_CONTINUATION_ACCOUNT_WARNING_AP_USERS);
// versus doing it in their respective documents (preq, credit memo)
// document is past full entry and
// user is a fiscal officer and a system parameter is set to allow viewing
// and if the continuation account indicator is set
if (isFiscalUser(document, user) && "Y".equals(showContinuationAccountWaringFO) && (document.isContinuationAccountIndicator())) {
KNSGlobalVariables.getMessageList().add(PurapKeyConstants.MESSAGE_CLOSED_OR_EXPIRED_ACCOUNTS_REPLACED);
}
// document is past full entry and
// user is an AP User and a system parameter is set to allow viewing
// and if the continuation account indicator is set
if (isAPUser(document, user) && "Y".equals(showContinuationAccountWaringAP) && (document.isContinuationAccountIndicator())) {
KNSGlobalVariables.getMessageList().add(PurapKeyConstants.MESSAGE_CLOSED_OR_EXPIRED_ACCOUNTS_REPLACED);
}
}
/**
* @see org.kuali.kfs.module.purap.document.service.AccountsPayableService#processExpiredOrClosedAccount(org.kuali.kfs.module.purap.businessobject.PurApAccountingLineBase,
* java.util.HashMap)
*/
@Override
public void processExpiredOrClosedAccount(PurApAccountingLineBase acctLineBase, HashMap<String, ExpiredOrClosedAccountEntry> expiredOrClosedAccountList) {
ExpiredOrClosedAccountEntry accountEntry = null;
String acctKey = acctLineBase.getChartOfAccountsCode() + "-" + acctLineBase.getAccountNumber();
if (expiredOrClosedAccountList.containsKey(acctKey)) {
accountEntry = expiredOrClosedAccountList.get(acctKey);
if (accountEntry.getOriginalAccount().isContinuationAccountMissing() == false) {
acctLineBase.setChartOfAccountsCode(accountEntry.getReplacementAccount().getChartOfAccountsCode());
acctLineBase.setAccountNumber(accountEntry.getReplacementAccount().getAccountNumber());
acctLineBase.refreshReferenceObject("chart");
acctLineBase.refreshReferenceObject("account");
}
}
}
/**
* Creates and adds a note indicating accounts replaced and what they replaced and attaches it to the document.
*
* @param document The accounts payable document to which we're adding the note.
* @param accounts The HashMap where the keys are the string representations of the chart and account of the
* original account and the values are the ExpiredOrClosedAccountEntry.
*/
protected void addContinuationAccountsNote(AccountsPayableDocument document, HashMap<String, ExpiredOrClosedAccountEntry> accounts) {
String noteText;
StringBuffer sb = new StringBuffer("");
ExpiredOrClosedAccountEntry accountEntry = null;
ExpiredOrClosedAccount originalAccount = null;
ExpiredOrClosedAccount replacementAccount = null;
// List the entries using entrySet()
Set entries = accounts.entrySet();
Iterator it = entries.iterator();
// loop through the accounts found to be expired/closed and add if they have a continuation account
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
accountEntry = (ExpiredOrClosedAccountEntry) entry.getValue();
originalAccount = accountEntry.getOriginalAccount();
replacementAccount = accountEntry.getReplacementAccount();
// only print out accounts that were replaced and not missing a continuation account
if (originalAccount.isContinuationAccountMissing() == false) {
String nteMsg = SpringContext.getBean(ConfigurationService.class).getPropertyValueAsString(PurapKeyConstants.KEY_ACCT_EXPIRED_NOTE);
sb.append(MessageFormat.format(nteMsg, new Object[] { originalAccount.getAccountString(), replacementAccount.getAccountString() }));
}
}
// if a note was created, add it to the document
if (sb.toString().length() > 0) {
try {
Note resetNote = documentService.createNoteFromDocument(document, sb.toString());
Principal kfs = KimApiServiceLocator.getIdentityService().getPrincipalByPrincipalName(KFSConstants.SYSTEM_USER);
resetNote.setAuthorUniversalIdentifier(kfs.getPrincipalId());
document.addNote(resetNote);
}
catch (Exception e) {
throw new RuntimeException(PurapConstants.REQ_UNABLE_TO_CREATE_NOTE + " " + e);
}
}
}
/**
* Gets the replacement account for the specified closed account.
* In this case it's the continuation account of the the specified account.
*
* @param account the specified account which is closed.
* @document the document the account is associated with.
* @return the replacement account for the specified account.
*/
protected Account getReplaceAccountForClosedAccount(Account account, AccountsPayableDocument document) {
if (account == null)
{
return null; // this should never happen
}
Account continueAccount = accountService.getByPrimaryId(account.getContinuationFinChrtOfAcctCd(), account.getContinuationAccountNumber());
return continueAccount;
}
/**
* Gets the replacement account for the specified expired account.
* In this case it's the continuation account of the the specified account.
*
* @param account the specified account which is expired.
* @document the document the account is associated with.
* @return the replacement account for the specified account.
*/
protected Account getReplaceAccountForExpiredAccount(Account account, AccountsPayableDocument document) {
if (account == null)
{
return null; // this should never happen
}
Account continueAccount = accountService.getByPrimaryId(account.getContinuationFinChrtOfAcctCd(), account.getContinuationAccountNumber());
return continueAccount;
}
/**
* Generates a list of replacement accounts for expired or closed accounts, as well as for expired/closed accounts without a continuation account.
*
* @param document The accounts payable document from which we're obtaining the purchase order id to be used
* to obtain the purchase order document, whose accounts we'll use to generate the list of
* replacement accounts for expired or closed accounts.
* @return The HashMap where the keys are the string representations of the chart and account
* of the original account and the values are the ExpiredOrClosedAccountEntry.
*/
protected HashMap<String, ExpiredOrClosedAccountEntry> expiredOrClosedAccountsList(AccountsPayableDocument document) {
// retrieve po from apdoc
PurchaseOrderDocument po = document.getPurchaseOrderDocument();
if (po == null && document instanceof VendorCreditMemoDocument) {
PaymentRequestDocument preq = ((VendorCreditMemoDocument)document).getPaymentRequestDocument();
if (preq == null)
{
return null; // this should never happen
}
po = ((VendorCreditMemoDocument)document).getPaymentRequestDocument().getPurchaseOrderDocument();
}
if (po == null)
{
return null; // this should never happen
}
// initialize
List<SourceAccountingLine> acctLines = purapAccountingService.generateSummary(po.getItemsActiveOnly());
HashMap<String, ExpiredOrClosedAccountEntry> eocAcctMap = new HashMap<String, ExpiredOrClosedAccountEntry>();
// loop through accounting lines
for (SourceAccountingLine acctLine : acctLines) {
Account account = accountService.getByPrimaryId(acctLine.getChartOfAccountsCode(), acctLine.getAccountNumber());
Account repAccount = null;
boolean replace = false;
// 1. if the account is closed, get the continuation account as replacement
if (!account.isActive()) {
repAccount = getReplaceAccountForClosedAccount(account, document);
replace = true;
}
// 2. if the account is C&G and is expired for more than 90 days, get the continuation account as replacement
else if (account.isExpired()) {
// retrieve extension limit (grace period)
String expirationExtensionDays = parameterService.getParameterValueAsString(ScrubberStep.class, KFSConstants.SystemGroupParameterNames.GL_SCRUBBER_VALIDATION_DAYS_OFFSET);
int expirationExtensionDaysInt = 90; // default to 90 days (approximately 3 months)
if (expirationExtensionDays.trim().length() > 0) {
expirationExtensionDaysInt = new Integer(expirationExtensionDays).intValue();
}
// if account is C&G and expired beyond grace period then get replacement
if ((account.isForContractsAndGrants() && dateTimeService.dateDiff(account.getAccountExpirationDate(), dateTimeService.getCurrentDate(), true) > expirationExtensionDaysInt)) {
repAccount = getReplaceAccountForExpiredAccount(account, document);
replace = true;
}
// otherwise if the account is not C&G, or it's expired within the grace period, do nothing
}
// if replacement needed, set up ExpiredOrClosedAccount entry and add it to the eocAcctMap
if (replace) {
ExpiredOrClosedAccountEntry eocAcctEntry = new ExpiredOrClosedAccountEntry();
ExpiredOrClosedAccount originAcct = new ExpiredOrClosedAccount(acctLine.getChartOfAccountsCode(), acctLine.getAccountNumber(), acctLine.getSubAccountNumber());
ExpiredOrClosedAccount replaceAcct = null;
if (repAccount == null) {
replaceAcct = new ExpiredOrClosedAccount();
originAcct.setContinuationAccountMissing(true);
}
else {
replaceAcct = new ExpiredOrClosedAccount(repAccount.getChartOfAccountsCode(), repAccount.getAccountNumber(), acctLine.getSubAccountNumber());
}
eocAcctEntry.setOriginalAccount(originAcct);
eocAcctEntry.setReplacementAccount(replaceAcct);
eocAcctMap.put(createChartAccountString(originAcct), eocAcctEntry);
}
}
return eocAcctMap;
}
/**
* Generates a list of replacement accounts for expired or closed accounts, as well as for expired/closed accounts without a continuation account.
*
* @param document The purchase order document whose accounts we'll use to generate the list of
* replacement accounts for expired or closed accounts.
* @return The HashMap where the keys are the string representations of the chart and account
* of the original account and the values are the ExpiredOrClosedAccountEntry.
*/
@Override
public HashMap<String, ExpiredOrClosedAccountEntry> expiredOrClosedAccountsList(PurchaseOrderDocument po) {
HashMap<String, ExpiredOrClosedAccountEntry> list = new HashMap<String, ExpiredOrClosedAccountEntry>();
ExpiredOrClosedAccountEntry entry = null;
ExpiredOrClosedAccount originalAcct = null;
ExpiredOrClosedAccount replaceAcct = null;
String chartAccount = null;
if (po != null) {
// get list of active accounts
List<SourceAccountingLine> accountList = purapAccountingService.generateSummary(po.getItemsActiveOnly());
// loop through accounts
for (SourceAccountingLine poAccountingLine : accountList) {
Account account = accountService.getByPrimaryId(poAccountingLine.getChartOfAccountsCode(), poAccountingLine.getAccountNumber());
entry = new ExpiredOrClosedAccountEntry();
originalAcct = new ExpiredOrClosedAccount(poAccountingLine.getChartOfAccountsCode(), poAccountingLine.getAccountNumber(), poAccountingLine.getSubAccountNumber());
if (!account.isActive()) {
// 1. if the account is closed, get the continuation account and add it to the list
Account continuationAccount = accountService.getByPrimaryId(account.getContinuationFinChrtOfAcctCd(), account.getContinuationAccountNumber());
if (continuationAccount == null) {
replaceAcct = new ExpiredOrClosedAccount();
originalAcct.setContinuationAccountMissing(true);
entry.setOriginalAccount(originalAcct);
entry.setReplacementAccount(replaceAcct);
list.put(createChartAccountString(originalAcct), entry);
}
else {
replaceAcct = new ExpiredOrClosedAccount(continuationAccount.getChartOfAccountsCode(), continuationAccount.getAccountNumber(), poAccountingLine.getSubAccountNumber());
entry.setOriginalAccount(originalAcct);
entry.setReplacementAccount(replaceAcct);
list.put(createChartAccountString(originalAcct), entry);
}
// 2. if the account is expired and the current date is <= 90 days from the expiration date, do nothing
// 3. if the account is expired and the current date is > 90 days from the expiration date, get the continuation
// account and add it to the list
}
else if (account.isExpired()) {
Account continuationAccount = accountService.getByPrimaryId(account.getContinuationFinChrtOfAcctCd(), account.getContinuationAccountNumber());
String expirationExtensionDays = parameterService.getParameterValueAsString(ScrubberStep.class, KFSConstants.SystemGroupParameterNames.GL_SCRUBBER_VALIDATION_DAYS_OFFSET);
int expirationExtensionDaysInt = 3 * 30; // default to 90 days (approximately 3 months)
if (expirationExtensionDays.trim().length() > 0) {
expirationExtensionDaysInt = new Integer(expirationExtensionDays).intValue();
}
// if account is C&G and expired then add to list.
if ((account.isForContractsAndGrants() && dateTimeService.dateDiff(account.getAccountExpirationDate(), dateTimeService.getCurrentDate(), true) > expirationExtensionDaysInt)) {
if (continuationAccount == null) {
replaceAcct = new ExpiredOrClosedAccount();
originalAcct.setContinuationAccountMissing(true);
entry.setOriginalAccount(originalAcct);
entry.setReplacementAccount(replaceAcct);
list.put(createChartAccountString(originalAcct), entry);
}
else {
replaceAcct = new ExpiredOrClosedAccount(continuationAccount.getChartOfAccountsCode(), continuationAccount.getAccountNumber(), poAccountingLine.getSubAccountNumber());
entry.setOriginalAccount(originalAcct);
entry.setReplacementAccount(replaceAcct);
list.put(createChartAccountString(originalAcct), entry);
}
}
// if account is not C&G, use the same account, do not replace
}
}
}
return list;
}
/**
* Creates a chart-account string.
*
* @param ecAccount The account whose chart and account number we're going to use to create the resulting String for this method.
* @return The string representing the chart and account number of the given ecAccount.
*/
protected String createChartAccountString(ExpiredOrClosedAccount ecAccount) {
StringBuffer buff = new StringBuffer("");
buff.append(ecAccount.getChartOfAccountsCode());
buff.append("-");
buff.append(ecAccount.getAccountNumber());
return buff.toString();
}
/**
* Determines if the user is a fiscal officer. Currently this only checks the doc and workflow status for approval requested
*
* @param document The document to be used to check the status code and whether the workflow approval is requested.
* @param user The current user.
* @return boolean true if the user is a fiscal officer.
*/
protected boolean isFiscalUser(AccountsPayableDocument document, Person user) {
boolean isFiscalUser = false;
if (PaymentRequestStatuses.APPDOC_AWAITING_FISCAL_REVIEW.equals(document.getApplicationDocumentStatus()) && document.getDocumentHeader().getWorkflowDocument().isApprovalRequested()) {
isFiscalUser = true;
}
return isFiscalUser;
}
/**
* Determines if the user is an AP user. Currently this only checks the doc and workflow status for approval requested
*
* @param document The document to be used to check the status code and whether the workflow approval is requested.
* @param user The current user.
* @return boolean true if the user is an AP User.
*/
protected boolean isAPUser(AccountsPayableDocument document, Person user) {
boolean isFiscalUser = false;
if ((PaymentRequestStatuses.APPDOC_AWAITING_ACCOUNTS_PAYABLE_REVIEW.equals(document.getApplicationDocumentStatus()) &&
document.getDocumentHeader().getWorkflowDocument().isApprovalRequested()) ||
PaymentRequestStatuses.APPDOC_IN_PROCESS.equals(document.getApplicationDocumentStatus())) {
isFiscalUser = true;
}
return isFiscalUser;
}
/**
* @see org.kuali.kfs.module.purap.document.service.AccountsPayableService#cancelAccountsPayableDocument(org.kuali.kfs.module.purap.document.AccountsPayableDocument, java.lang.String)
*/
@Override
public void cancelAccountsPayableDocument(AccountsPayableDocument apDocument, String currentNodeName) {
if (purapService.isFullDocumentEntryCompleted(apDocument)) {
purapGeneralLedgerService.generateEntriesCancelAccountsPayableDocument(apDocument);
}
AccountsPayableDocumentSpecificService accountsPayableDocumentSpecificService = apDocument.getDocumentSpecificService();
accountsPayableDocumentSpecificService.updateStatusByNode(currentNodeName, apDocument);
// close/reopen purchase order.
accountsPayableDocumentSpecificService.takePurchaseOrderCancelAction(apDocument);
}
/**
* @see org.kuali.kfs.module.purap.document.service.AccountsPayableService#cancelAccountsPayableDocumentByCheckingDocumentStatus(org.kuali.kfs.module.purap.document.AccountsPayableDocument, java.lang.String)
*/
@Override
public void cancelAccountsPayableDocumentByCheckingDocumentStatus(AccountsPayableDocument document, String noteText) throws Exception {
DocumentService documentService = SpringContext.getBean(DocumentService.class);
if (PurapConstants.CreditMemoStatuses.APPDOC_IN_PROCESS.equals(document.getApplicationDocumentStatus())) {
//prior to submit, just call regular cancel logic
documentService.cancelDocument(document, noteText);
}
else if (PurapConstants.CreditMemoStatuses.APPDOC_AWAITING_ACCOUNTS_PAYABLE_REVIEW.equals(document.getApplicationDocumentStatus())) {
//while awaiting AP approval, just call regular disapprove logic as user will have action request
//need to set the user session as kfs because this document needs to be disapproved
// in the context of kfs user because that is where it is in the route path.
UserSession originalUserSession = GlobalVariables.getUserSession();
GlobalVariables.setUserSession(new UserSession(KFSConstants.SYSTEM_USER));
// JHK : You need to load the Workflow Document in the context of the "kfs" user so that user account is passed to the workflow engine on the call below
// Document docLoadedAsKfs = documentService.getByDocumentHeaderId(document.getDocumentNumber());
// KewApiServiceLocator.get
WorkflowDocument workflowDocument = KRADServiceLocatorWeb.getWorkflowDocumentService().loadWorkflowDocument(document.getDocumentNumber(), GlobalVariables.getUserSession().getPerson());
document.getDocumentHeader().setWorkflowDocument(workflowDocument);
documentService.superUserDisapproveDocumentWithoutSaving(document, noteText);
//restore the original user session
GlobalVariables.setUserSession(originalUserSession);
}
else if (document instanceof PaymentRequestDocument && PurapConstants.PaymentRequestStatuses.APPDOC_AWAITING_FISCAL_REVIEW.equals(document.getApplicationDocumentStatus()) && ((PaymentRequestDocument)document).isPaymentRequestedCancelIndicator()) {
// special logic to disapprove PREQ as the fiscal officer
DocumentActionParameters.Builder p = DocumentActionParameters.Builder.create(document.getDocumentNumber(), document.getLastActionPerformedByPersonId());
p.setAnnotation("Document cancelled after requested cancel by "+GlobalVariables.getUserSession().getPrincipalName());
KewApiServiceLocator.getWorkflowDocumentActionsService().disapprove( p.build() );
}
else {
UserSession originalUserSession = GlobalVariables.getUserSession();
WorkflowDocument originalWorkflowDocument = document.getDocumentHeader().getWorkflowDocument();
//any other time, perform special logic to cancel the document
if (!document.getDocumentHeader().getWorkflowDocument().isApproved()) {
try {
// person canceling may not have an action requested on the document
Person userRequestedCancel = SpringContext.getBean(PersonService.class).getPerson(document.getLastActionPerformedByPersonId());
GlobalVariables.setUserSession(new UserSession(KFSConstants.SYSTEM_USER));
WorkflowDocumentService workflowDocumentService = SpringContext.getBean(WorkflowDocumentService.class);
WorkflowDocument newWorkflowDocument = workflowDocumentService.loadWorkflowDocument(document.getDocumentNumber(), GlobalVariables.getUserSession().getPerson());
document.getDocumentHeader().setWorkflowDocument(newWorkflowDocument);
String annotation = "Document Cancelled by user " + originalUserSession.getPerson().getName() + " (" + originalUserSession.getPerson().getPrincipalName() + ")";
if (ObjectUtils.isNotNull(userRequestedCancel)) {
annotation.concat(" per request of user " + userRequestedCancel.getName() + " (" + userRequestedCancel.getPrincipalName() + ")");
}
documentService.superUserDisapproveDocument(document, annotation);
}
finally {
GlobalVariables.setUserSession(originalUserSession);
document.getDocumentHeader().setWorkflowDocument(originalWorkflowDocument);
}
}
else {
// call gl method here (no reason for post processing since workflow done)
SpringContext.getBean(AccountsPayableService.class).cancelAccountsPayableDocument(document, "");
document.getDocumentHeader().getWorkflowDocument().logAnnotation("Document Cancelled by user " + originalUserSession.getPerson().getName() + " (" + originalUserSession.getPerson().getPrincipalName() + ")");
}
}
Note noteObj = documentService.createNoteFromDocument(document, noteText);
document.addNote(noteObj);
SpringContext.getBean(NoteService.class).save(noteObj);
}
/**
* @see org.kuali.kfs.module.purap.document.service.AccountsPayableService#performLogicForFullEntryCompleted(org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument)
*/
@Override
public void performLogicForFullEntryCompleted(PurchasingAccountsPayableDocument purapDocument) {
AccountsPayableDocument apDocument = (AccountsPayableDocument)purapDocument;
AccountsPayableDocumentSpecificService accountsPayableDocumentSpecificService = apDocument.getDocumentSpecificService();
// eliminate unentered items
purapService.deleteUnenteredItems(apDocument);
// change accounts from percents to dollars
purapAccountingService.updateAccountAmounts(apDocument);
//set the AP approval date always when the GL entries are created (treated more of an AP processed date)
apDocument.setAccountsPayableApprovalTimestamp(dateTimeService.getCurrentTimestamp());
// save for persistence
SpringContext.getBean(BusinessObjectService.class).save(apDocument);
// do GL entries for document creation
accountsPayableDocumentSpecificService.generateGLEntriesCreateAccountsPayableDocument(apDocument);
}
/**
* @see org.kuali.kfs.module.purap.document.service.AccountsPayableService#updateItemList(org.kuali.kfs.module.purap.document.AccountsPayableDocument)
*/
@Override
public void updateItemList(AccountsPayableDocument apDocument) {
// don't run the following if past full entry
if (purapService.isFullDocumentEntryCompleted(apDocument)) {
return;
}
if (apDocument instanceof VendorCreditMemoDocument) {
VendorCreditMemoDocument cm = (VendorCreditMemoDocument) apDocument;
if (cm.isSourceDocumentPaymentRequest()) {
// just update encumberances, items shouldn't change, get to them through po (or through preq)
List<PaymentRequestItem> items = cm.getPaymentRequestDocument().getItems();
for (PaymentRequestItem preqItem : items) {
// skip inactive and below the line
if (preqItem.getItemType().isAdditionalChargeIndicator()) {
continue;
}
PurchaseOrderItem poItem = preqItem.getPurchaseOrderItem();
CreditMemoItem cmItem = (CreditMemoItem) cm.getAPItemFromPOItem(poItem);
// take invoiced quantities from the lower of the preq and po if different
updateEncumberances(preqItem, poItem, cmItem);
}
}
else if (cm.isSourceDocumentPurchaseOrder()) {
PurchaseOrderDocument po = purchaseOrderService.getCurrentPurchaseOrder(apDocument.getPurchaseOrderIdentifier());
List<PurchaseOrderItem> poItems = po.getItems();
List<CreditMemoItem> cmItems = cm.getItems();
// iterate through the above the line poItems to find matching
for (PurchaseOrderItem purchaseOrderItem : poItems) {
// skip inactive and below the line
if (purchaseOrderItem.getItemType().isAdditionalChargeIndicator()) {
continue;
}
CreditMemoItem cmItem = (CreditMemoItem) cm.getAPItemFromPOItem(purchaseOrderItem);
// check if any action needs to be taken on the items (i.e. add for new eligible items or remove for ineligible)
if (apDocument.getDocumentSpecificService().poItemEligibleForAp(apDocument, purchaseOrderItem)) {
// if eligible and not there - add
if (ObjectUtils.isNull(cmItem)) {
CreditMemoItem cmi = new CreditMemoItem(cm, purchaseOrderItem);
cmi.setPurapDocument(apDocument);
cmItems.add(cmi);
}
else {
// is eligible and on doc, update encumberances
// (this is only qty and amount for now NOTE we should also update other key fields, like description
// etc in case ammendment modified a line
updateEncumberance(purchaseOrderItem, cmItem);
}
}
else { // if not eligible and there - remove
if (ObjectUtils.isNotNull(cmItem)) {
cmItems.remove(cmItem);
// don't update encumberance
continue;
}
}
}
} // else do nothing
return;
// finally update encumbrances
}
else if (apDocument instanceof PaymentRequestDocument) {
// get a fresh purchase order
PurchaseOrderDocument po = purchaseOrderService.getCurrentPurchaseOrder(apDocument.getPurchaseOrderIdentifier());
PaymentRequestDocument preq = (PaymentRequestDocument) apDocument;
List<PurchaseOrderItem> poItems = po.getItems();
List<PaymentRequestItem> preqItems = preq.getItems();
// iterate through the above the line poItems to find matching
for (PurchaseOrderItem purchaseOrderItem : poItems) {
// skip below the line
if (purchaseOrderItem.getItemType().isAdditionalChargeIndicator()) {
continue;
}
PaymentRequestItem preqItem = (PaymentRequestItem) preq.getAPItemFromPOItem(purchaseOrderItem);
// check if any action needs to be taken on the items (i.e. add for new eligible items or remove for ineligible)
if (apDocument.getDocumentSpecificService().poItemEligibleForAp(apDocument, purchaseOrderItem)) {
// if eligible and not there - add
if (ObjectUtils.isNull(preqItem)) {
PaymentRequestItem pri = new PaymentRequestItem(purchaseOrderItem, preq);
pri.setPurapDocument(apDocument);
preqItems.add(pri);
}
else {
updatePossibleAmmendedFields(purchaseOrderItem, preqItem);
}
}
else { // if not eligible and there - remove
if (ObjectUtils.isNotNull(preqItem)) {
preqItems.remove(preqItem);
}
}
}
}
}
/**
* Updates fields that could've been changed on amendment.
*
* @param sourceItem The purchase order item from which we're getting the unit price, catalog number and description to be set in the destItem.
* @param destItem The payment request item to which we're setting the unit price, catalog number and description.
*/
protected void updatePossibleAmmendedFields(PurchaseOrderItem sourceItem, PaymentRequestItem destItem) {
destItem.setPurchaseOrderItemUnitPrice(sourceItem.getItemUnitPrice());
destItem.setItemCatalogNumber(sourceItem.getItemCatalogNumber());
destItem.setItemDescription(sourceItem.getItemDescription());
}
/**
* Updates encumberances.
*
* @param preqItem The payment request item from which we're obtaining the item quantity, unit price and extended price.
* @param poItem The purchase order item from which we're obtaining the invoice total quantity, unit price and invoice total amount.
* @param cmItem The credit memo item whose invoice total quantity, unit price and extended price are to be updated.
*/
protected void updateEncumberances(PaymentRequestItem preqItem, PurchaseOrderItem poItem, CreditMemoItem cmItem) {
if (poItem.getItemInvoicedTotalQuantity() != null && preqItem.getItemQuantity() != null && poItem.getItemInvoicedTotalQuantity().isLessThan(preqItem.getItemQuantity())) {
cmItem.setPreqInvoicedTotalQuantity(poItem.getItemInvoicedTotalQuantity());
cmItem.setPreqUnitPrice(poItem.getItemUnitPrice());
cmItem.setPreqTotalAmount(poItem.getItemInvoicedTotalAmount());
}
else {
cmItem.setPreqInvoicedTotalQuantity(preqItem.getItemQuantity());
cmItem.setPreqUnitPrice(preqItem.getItemUnitPrice());
cmItem.setPreqTotalAmount(preqItem.getTotalAmount());
}
}
/**
* Updates the encumberance related fields.
*
* @param purchaseOrderItem The purchase order item from which we're obtaining the invoice total quantity, unit price and invoice total amount.
* @param cmItem The credit memo item whose invoice total quantity, unit price and extended price are to be updated.
*/
protected void updateEncumberance(PurchaseOrderItem purchaseOrderItem, CreditMemoItem cmItem) {
cmItem.setPoInvoicedTotalQuantity(purchaseOrderItem.getItemInvoicedTotalQuantity());
cmItem.setPreqUnitPrice(purchaseOrderItem.getItemUnitPrice());
cmItem.setPoTotalAmount(purchaseOrderItem.getItemInvoicedTotalAmount());
}
/**
* @see org.kuali.kfs.module.purap.document.service.AccountsPayableService#purchaseOrderItemEligibleForPayment(org.kuali.kfs.module.purap.businessobject.PurchaseOrderItem)
*/
@Override
public boolean purchaseOrderItemEligibleForPayment(PurchaseOrderItem poi) {
if (ObjectUtils.isNull(poi)) {
throw new RuntimeException("item null in purchaseOrderItemEligibleForPayment ... this should never happen");
}
// if the po item is not active... skip it
if (!poi.isItemActiveIndicator()) {
return false;
}
ItemType poiType = poi.getItemType();
if (poiType.isQuantityBasedGeneralLedgerIndicator()) {
if (poi.getItemQuantity().isGreaterThan(poi.getItemInvoicedTotalQuantity())) {
return true;
}
return false;
}
else { // not quantity based
if (poi.getItemOutstandingEncumberedAmount().isGreaterThan(KualiDecimal.ZERO)) {
return true;
}
return false;
}
}
/**
* @see org.kuali.kfs.module.purap.document.service.AccountsPayableService#canCopyAccountingLinesWithZeroAmount()
*/
@Override
public boolean canCopyAccountingLinesWithZeroAmount() {
boolean canCopyLine = false;
// get parameter to see if accounting line with zero dollar amount can be copied from PO to PREQ.
String copyZeroAmountLine = parameterService.getParameterValueAsString(KfsParameterConstants.PURCHASING_DOCUMENT.class, PurapParameterConstants.COPY_ACCOUNTING_LINES_WITH_ZERO_AMOUNT_FROM_PO_TO_PREQ_IND);
if ("Y".equalsIgnoreCase(copyZeroAmountLine)) {
return true;
}
return canCopyLine;
}
}