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.service.impl; 017 018 import static org.kuali.kfs.module.purap.PurapConstants.HUNDRED; 019 import static org.kuali.kfs.module.purap.PurapConstants.PURAP_ORIGIN_CODE; 020 import static org.kuali.kfs.sys.KFSConstants.BALANCE_TYPE_EXTERNAL_ENCUMBRANCE; 021 import static org.kuali.kfs.sys.KFSConstants.ENCUMB_UPDT_REFERENCE_DOCUMENT_CD; 022 import static org.kuali.kfs.sys.KFSConstants.GL_CREDIT_CODE; 023 import static org.kuali.kfs.sys.KFSConstants.GL_DEBIT_CODE; 024 import static org.kuali.kfs.sys.KFSConstants.MONTH1; 025 import static org.kuali.rice.kns.util.KualiDecimal.ZERO; 026 027 import java.math.BigDecimal; 028 import java.util.ArrayList; 029 import java.util.Collections; 030 import java.util.HashMap; 031 import java.util.Iterator; 032 import java.util.List; 033 import java.util.Map; 034 035 import org.kuali.kfs.coa.businessobject.ObjectCode; 036 import org.kuali.kfs.coa.businessobject.SubObjectCode; 037 import org.kuali.kfs.coa.service.ObjectCodeService; 038 import org.kuali.kfs.coa.service.SubObjectCodeService; 039 import org.kuali.kfs.module.purap.PurapConstants; 040 import org.kuali.kfs.module.purap.PurapConstants.PurapDocTypeCodes; 041 import org.kuali.kfs.module.purap.businessobject.AccountsPayableSummaryAccount; 042 import org.kuali.kfs.module.purap.businessobject.CreditMemoItem; 043 import org.kuali.kfs.module.purap.businessobject.ItemType; 044 import org.kuali.kfs.module.purap.businessobject.PaymentRequestItem; 045 import org.kuali.kfs.module.purap.businessobject.PurApItemUseTax; 046 import org.kuali.kfs.module.purap.businessobject.PurchaseOrderAccount; 047 import org.kuali.kfs.module.purap.businessobject.PurchaseOrderItem; 048 import org.kuali.kfs.module.purap.document.AccountsPayableDocument; 049 import org.kuali.kfs.module.purap.document.PaymentRequestDocument; 050 import org.kuali.kfs.module.purap.document.PurchaseOrderDocument; 051 import org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument; 052 import org.kuali.kfs.module.purap.document.VendorCreditMemoDocument; 053 import org.kuali.kfs.module.purap.document.service.PaymentRequestService; 054 import org.kuali.kfs.module.purap.document.service.PurapService; 055 import org.kuali.kfs.module.purap.document.service.PurchaseOrderService; 056 import org.kuali.kfs.module.purap.service.PurapAccountRevisionService; 057 import org.kuali.kfs.module.purap.service.PurapAccountingService; 058 import org.kuali.kfs.module.purap.service.PurapGeneralLedgerService; 059 import org.kuali.kfs.module.purap.util.SummaryAccount; 060 import org.kuali.kfs.module.purap.util.UseTaxContainer; 061 import org.kuali.kfs.sys.KFSConstants; 062 import org.kuali.kfs.sys.businessobject.AccountingLine; 063 import org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntry; 064 import org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntrySequenceHelper; 065 import org.kuali.kfs.sys.businessobject.SourceAccountingLine; 066 import org.kuali.kfs.sys.businessobject.UniversityDate; 067 import org.kuali.kfs.sys.context.SpringContext; 068 import org.kuali.kfs.sys.service.GeneralLedgerPendingEntryService; 069 import org.kuali.kfs.sys.service.UniversityDateService; 070 import org.kuali.rice.kns.service.BusinessObjectService; 071 import org.kuali.rice.kns.service.DateTimeService; 072 import org.kuali.rice.kns.service.KualiRuleService; 073 import org.kuali.rice.kns.service.ParameterService; 074 import org.kuali.rice.kns.util.KualiDecimal; 075 import org.kuali.rice.kns.util.ObjectUtils; 076 import org.springframework.transaction.annotation.Transactional; 077 078 @Transactional 079 public class PurapGeneralLedgerServiceImpl implements PurapGeneralLedgerService { 080 private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(PurapGeneralLedgerServiceImpl.class); 081 082 private BusinessObjectService businessObjectService; 083 private DateTimeService dateTimeService; 084 private GeneralLedgerPendingEntryService generalLedgerPendingEntryService; 085 private KualiRuleService kualiRuleService; 086 private PaymentRequestService paymentRequestService; 087 private ParameterService parameterService; 088 private PurapAccountingService purapAccountingService; 089 private PurchaseOrderService purchaseOrderService; 090 private UniversityDateService universityDateService; 091 private ObjectCodeService objectCodeService; 092 private SubObjectCodeService subObjectCodeService; 093 094 /** 095 * @see org.kuali.kfs.module.purap.service.PurapGeneralLedgerService#customizeGeneralLedgerPendingEntry(org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument, 096 * org.kuali.kfs.sys.businessobject.AccountingLine, org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntry, 097 * java.lang.Integer, java.lang.String, java.lang.String, boolean) 098 */ 099 public void customizeGeneralLedgerPendingEntry(PurchasingAccountsPayableDocument purapDocument, AccountingLine accountingLine, GeneralLedgerPendingEntry explicitEntry, Integer referenceDocumentNumber, String debitCreditCode, String docType, boolean isEncumbrance) { 100 LOG.debug("customizeGeneralLedgerPendingEntry() started"); 101 102 explicitEntry.setDocumentNumber(purapDocument.getDocumentNumber()); 103 explicitEntry.setTransactionLedgerEntryDescription(entryDescription(purapDocument.getVendorName())); 104 explicitEntry.setFinancialSystemOriginationCode(PURAP_ORIGIN_CODE); 105 106 // Always make the referring document the PO for all PURAP docs except for CM against a vendor. 107 // This is required for encumbrance entries. It's not required for actual/liability 108 // entries, but it makes things easier to deal with. If vendor, leave referring stuff blank. 109 if (ObjectUtils.isNotNull(referenceDocumentNumber)) { 110 explicitEntry.setReferenceFinancialDocumentNumber(referenceDocumentNumber.toString()); 111 explicitEntry.setReferenceFinancialDocumentTypeCode(PurapDocTypeCodes.PO_DOCUMENT); 112 explicitEntry.setReferenceFinancialSystemOriginationCode(PURAP_ORIGIN_CODE); 113 } 114 115 // DEFAULT TO USE CURRENT; don't use FY on doc in case it's a prior year 116 UniversityDate uDate = universityDateService.getCurrentUniversityDate(); 117 explicitEntry.setUniversityFiscalYear(uDate.getUniversityFiscalYear()); 118 explicitEntry.setUniversityFiscalPeriodCode(uDate.getUniversityFiscalAccountingPeriod()); 119 120 if (PurapDocTypeCodes.PO_DOCUMENT.equals(docType)) { 121 if (purapDocument.getPostingYear().compareTo(uDate.getUniversityFiscalYear()) > 0) { 122 // USE NEXT AS SET ON PO; POs can be forward dated to not encumber until next fiscal year 123 explicitEntry.setUniversityFiscalYear(purapDocument.getPostingYear()); 124 explicitEntry.setUniversityFiscalPeriodCode(MONTH1); 125 } 126 } 127 else if (PurapDocTypeCodes.PAYMENT_REQUEST_DOCUMENT.equals(docType)) { 128 PaymentRequestDocument preq = (PaymentRequestDocument) purapDocument; 129 if (paymentRequestService.allowBackpost(preq)) { 130 LOG.debug("createGlPendingTransaction() within range to allow backpost; posting entry to period 12 of previous FY"); 131 explicitEntry.setUniversityFiscalYear(uDate.getUniversityFiscalYear() - 1); 132 explicitEntry.setUniversityFiscalPeriodCode(KFSConstants.MONTH12); 133 } 134 135 // if alternate payee is paid for non-primary vendor payment, send alternate vendor name in GL desc 136 if (preq.getAlternateVendorHeaderGeneratedIdentifier() != null && preq.getAlternateVendorDetailAssignedIdentifier() != null && preq.getVendorHeaderGeneratedIdentifier().compareTo(preq.getAlternateVendorHeaderGeneratedIdentifier()) == 0 && preq.getVendorDetailAssignedIdentifier().compareTo(preq.getAlternateVendorDetailAssignedIdentifier()) == 0) { 137 explicitEntry.setTransactionLedgerEntryDescription(entryDescription(preq.getPurchaseOrderDocument().getAlternateVendorName())); 138 } 139 140 } 141 else if (PurapDocTypeCodes.CREDIT_MEMO_DOCUMENT.equals(docType)) { 142 VendorCreditMemoDocument cm = (VendorCreditMemoDocument) purapDocument; 143 if (cm.isSourceDocumentPaymentRequest()) { 144 // if CM is off of PREQ, use vendor name associated with PREQ (primary or alternate) 145 PaymentRequestDocument cmPR = cm.getPaymentRequestDocument(); 146 PurchaseOrderDocument cmPO = cm.getPurchaseOrderDocument(); 147 // if alternate payee is paid for non-primary vendor payment, send alternate vendor name in GL desc 148 if (cmPR.getAlternateVendorHeaderGeneratedIdentifier() != null && cmPR.getAlternateVendorDetailAssignedIdentifier() != null && cmPR.getVendorHeaderGeneratedIdentifier().compareTo(cmPR.getAlternateVendorHeaderGeneratedIdentifier()) == 0 && cmPR.getVendorDetailAssignedIdentifier().compareTo(cmPR.getAlternateVendorDetailAssignedIdentifier()) == 0) { 149 explicitEntry.setTransactionLedgerEntryDescription(entryDescription(cmPO.getAlternateVendorName())); 150 } 151 } 152 } 153 else { 154 throw new IllegalArgumentException("purapDocument (doc #" + purapDocument.getDocumentNumber() + ") is invalid"); 155 } 156 157 ObjectCode objectCode = objectCodeService.getByPrimaryId(explicitEntry.getUniversityFiscalYear(), explicitEntry.getChartOfAccountsCode(), explicitEntry.getFinancialObjectCode()); 158 if (ObjectUtils.isNotNull(objectCode)) { 159 explicitEntry.setFinancialObjectTypeCode(objectCode.getFinancialObjectTypeCode()); 160 } 161 162 SubObjectCode subObjectCode = subObjectCodeService.getByPrimaryId(explicitEntry.getUniversityFiscalYear(), explicitEntry.getChartOfAccountsCode(), explicitEntry.getAccountNumber(), explicitEntry.getFinancialObjectCode(), explicitEntry.getFinancialSubObjectCode()); 163 if (ObjectUtils.isNotNull(subObjectCode)) { 164 explicitEntry.setFinancialSubObjectCode(subObjectCode.getFinancialSubObjectCode()); 165 } 166 167 if (isEncumbrance) { 168 explicitEntry.setFinancialBalanceTypeCode(BALANCE_TYPE_EXTERNAL_ENCUMBRANCE); 169 170 // D - means the encumbrance is based on the document number 171 // R - means the encumbrance is based on the referring document number 172 // All encumbrances should set the update code to 'R' regardless of if they were created by the PO, PREQ, or CM 173 explicitEntry.setTransactionEncumbranceUpdateCode(ENCUMB_UPDT_REFERENCE_DOCUMENT_CD); 174 } 175 176 // if the amount is negative, flip the D/C indicator 177 if (accountingLine.getAmount().doubleValue() < 0) { 178 if (GL_CREDIT_CODE.equals(debitCreditCode)) { 179 explicitEntry.setTransactionDebitCreditCode(GL_DEBIT_CODE); 180 } 181 else { 182 explicitEntry.setTransactionDebitCreditCode(GL_CREDIT_CODE); 183 } 184 } 185 else { 186 explicitEntry.setTransactionDebitCreditCode(debitCreditCode); 187 } 188 189 }// end purapCustomizeGeneralLedgerPendingEntry() 190 191 /** 192 * @see org.kuali.kfs.module.purap.service.PurapGeneralLedgerService#generateEntriesCancelAccountsPayableDocument(org.kuali.kfs.module.purap.document.AccountsPayableDocument) 193 */ 194 public void generateEntriesCancelAccountsPayableDocument(AccountsPayableDocument apDocument) { 195 LOG.debug("generateEntriesCancelAccountsPayableDocument() started"); 196 if (apDocument instanceof PaymentRequestDocument) { 197 LOG.info("generateEntriesCancelAccountsPayableDocument() cancel PaymentRequestDocument"); 198 generateEntriesCancelPaymentRequest((PaymentRequestDocument) apDocument); 199 } 200 else if (apDocument instanceof VendorCreditMemoDocument) { 201 LOG.info("generateEntriesCancelAccountsPayableDocument() cancel CreditMemoDocument"); 202 generateEntriesCancelCreditMemo((VendorCreditMemoDocument) apDocument); 203 } 204 else { 205 // doc not found 206 } 207 } 208 209 /** 210 * @see org.kuali.kfs.module.purap.service.PurapGeneralLedgerService#generateEntriesCreatePaymentRequest(org.kuali.kfs.module.purap.document.PaymentRequestDocument) 211 */ 212 public void generateEntriesCreatePaymentRequest(PaymentRequestDocument preq) { 213 LOG.debug("generateEntriesCreatePaymentRequest() started"); 214 List<SourceAccountingLine> encumbrances = relieveEncumbrance(preq); 215 List<SummaryAccount> summaryAccounts = purapAccountingService.generateSummaryAccountsWithNoZeroTotalsNoUseTax(preq); 216 generateEntriesPaymentRequest(preq, encumbrances, summaryAccounts, CREATE_PAYMENT_REQUEST); 217 } 218 219 /** 220 * Called from generateEntriesCancelAccountsPayableDocument() for Payment Request Document 221 * 222 * @param preq Payment Request document to cancel 223 * @see org.kuali.kfs.module.purap.service.PurapGeneralLedgerService#generateEntriesCancelAccountsPayableDocument(org.kuali.kfs.module.purap.document.AccountsPayableDocument) 224 */ 225 protected void generateEntriesCancelPaymentRequest(PaymentRequestDocument preq) { 226 LOG.debug("generateEntriesCreatePaymentRequest() started"); 227 List<SourceAccountingLine> encumbrances = reencumberEncumbrance(preq); 228 List<SummaryAccount> summaryAccounts = purapAccountingService.generateSummaryAccountsWithNoZeroTotalsNoUseTax(preq); 229 generateEntriesPaymentRequest(preq, encumbrances, summaryAccounts, CANCEL_PAYMENT_REQUEST); 230 } 231 232 /** 233 * @see org.kuali.kfs.module.purap.service.PurapGeneralLedgerService#generateEntriesModifyPaymentRequest(org.kuali.kfs.module.purap.document.PaymentRequestDocument) 234 */ 235 public void generateEntriesModifyPaymentRequest(PaymentRequestDocument preq) { 236 LOG.debug("generateEntriesModifyPaymentRequest() started"); 237 238 Map<SourceAccountingLine, KualiDecimal> actualsPositive = new HashMap<SourceAccountingLine, KualiDecimal>(); 239 List<SourceAccountingLine> newAccountingLines = purapAccountingService.generateSummaryWithNoZeroTotalsNoUseTax(preq.getItems()); 240 for (SourceAccountingLine newAccount : newAccountingLines) { 241 actualsPositive.put(newAccount, newAccount.getAmount()); 242 LOG.debug("generateEntriesModifyPaymentRequest() actualsPositive: " + newAccount.getAccountNumber() + " = " + newAccount.getAmount()); 243 } 244 245 Map<SourceAccountingLine, KualiDecimal> actualsNegative = new HashMap<SourceAccountingLine, KualiDecimal>(); 246 List<AccountsPayableSummaryAccount> oldAccountingLines = purapAccountingService.getAccountsPayableSummaryAccounts(preq.getPurapDocumentIdentifier(), PurapDocTypeCodes.PAYMENT_REQUEST_DOCUMENT); 247 248 for (AccountsPayableSummaryAccount oldAccount : oldAccountingLines) { 249 actualsNegative.put(oldAccount.generateSourceAccountingLine(), oldAccount.getAmount()); 250 LOG.debug("generateEntriesModifyPaymentRequest() actualsNegative: " + oldAccount.getAccountNumber() + " = " + oldAccount.getAmount()); 251 } 252 253 // Add the positive entries and subtract the negative entries 254 Map<SourceAccountingLine, KualiDecimal> glEntries = new HashMap<SourceAccountingLine, KualiDecimal>(); 255 256 // Combine the two maps (copy all the positive entries) 257 LOG.debug("generateEntriesModifyPaymentRequest() Combine positive/negative entries"); 258 glEntries.putAll(actualsPositive); 259 260 for (Iterator<SourceAccountingLine> iter = actualsNegative.keySet().iterator(); iter.hasNext();) { 261 SourceAccountingLine key = (SourceAccountingLine) iter.next(); 262 263 KualiDecimal amt; 264 if (glEntries.containsKey(key)) { 265 amt = (KualiDecimal) glEntries.get(key); 266 amt = amt.subtract((KualiDecimal) actualsNegative.get(key)); 267 } 268 else { 269 amt = ZERO; 270 amt = amt.subtract((KualiDecimal) actualsNegative.get(key)); 271 } 272 glEntries.put(key, amt); 273 } 274 275 List<SummaryAccount> summaryAccounts = new ArrayList<SummaryAccount>(); 276 for (Iterator<SourceAccountingLine> iter = glEntries.keySet().iterator(); iter.hasNext();) { 277 SourceAccountingLine account = (SourceAccountingLine) iter.next(); 278 KualiDecimal amount = (KualiDecimal) glEntries.get(account); 279 if (ZERO.compareTo(amount) != 0) { 280 account.setAmount(amount); 281 SummaryAccount sa = new SummaryAccount(account); 282 summaryAccounts.add(sa); 283 } 284 } 285 286 LOG.debug("generateEntriesModifyPaymentRequest() Generate GL entries"); 287 generateEntriesPaymentRequest(preq, null, summaryAccounts, MODIFY_PAYMENT_REQUEST); 288 } 289 290 /** 291 * @see org.kuali.kfs.module.purap.service.PurapGeneralLedgerService#generateEntriesCreateCreditMemo(org.kuali.kfs.module.purap.document.CreditMemoDocument) 292 */ 293 public void generateEntriesCreateCreditMemo(VendorCreditMemoDocument cm) { 294 LOG.debug("generateEntriesCreateCreditMemo() started"); 295 generateEntriesCreditMemo(cm, CREATE_CREDIT_MEMO); 296 } 297 298 /** 299 * Called from generateEntriesCancelAccountsPayableDocument() for Payment Request Document 300 * 301 * @param preq Payment Request document to cancel 302 * @see org.kuali.kfs.module.purap.service.PurapGeneralLedgerService#generateEntriesCancelAccountsPayableDocument(org.kuali.kfs.module.purap.document.AccountsPayableDocument) 303 */ 304 protected void generateEntriesCancelCreditMemo(VendorCreditMemoDocument cm) { 305 LOG.debug("generateEntriesCancelCreditMemo() started"); 306 generateEntriesCreditMemo(cm, CANCEL_CREDIT_MEMO); 307 } 308 309 /** 310 * Retrieves the next available sequence number from the general ledger pending entry table for this document 311 * 312 * @param documentNumber Document number to find next sequence number 313 * @return Next available sequence number 314 */ 315 protected int getNextAvailableSequence(String documentNumber) { 316 LOG.debug("getNextAvailableSequence() started"); 317 Map fieldValues = new HashMap(); 318 fieldValues.put("financialSystemOriginationCode", PURAP_ORIGIN_CODE); 319 fieldValues.put("documentNumber", documentNumber); 320 int count = businessObjectService.countMatching(GeneralLedgerPendingEntry.class, fieldValues); 321 return count + 1; 322 } 323 324 /** 325 * Creates the general ledger entries for Payment Request actions. 326 * 327 * @param preq Payment Request document to create entries 328 * @param encumbrances List of encumbrance accounts if applies 329 * @param accountingLines List of preq accounts to create entries 330 * @param processType Type of process (create, modify, cancel) 331 * @return Boolean returned indicating whether entry creation succeeded 332 */ 333 protected boolean generateEntriesPaymentRequest(PaymentRequestDocument preq, List encumbrances, List summaryAccounts, String processType) { 334 LOG.debug("generateEntriesPaymentRequest() started"); 335 boolean success = true; 336 preq.setGeneralLedgerPendingEntries(new ArrayList()); 337 338 /* 339 * Can't let generalLedgerPendingEntryService just create all the entries because we need the sequenceHelper to carry over 340 * from the encumbrances to the actuals and also because we need to tell the PaymentRequestDocumentRule customize entry 341 * method how to customize differently based on if creating an encumbrance or actual. 342 */ 343 GeneralLedgerPendingEntrySequenceHelper sequenceHelper = new GeneralLedgerPendingEntrySequenceHelper(getNextAvailableSequence(preq.getDocumentNumber())); 344 345 // when cancelling a PREQ, do not book encumbrances if PO is CLOSED 346 if (encumbrances != null && !(CANCEL_PAYMENT_REQUEST.equals(processType) && PurapConstants.PurchaseOrderStatuses.CLOSED.equals(preq.getPurchaseOrderDocument().getStatusCode()))) { 347 LOG.debug("generateEntriesPaymentRequest() generate encumbrance entries"); 348 if (CREATE_PAYMENT_REQUEST.equals(processType)) { 349 // on create, use CREDIT code for encumbrances 350 preq.setDebitCreditCodeForGLEntries(GL_CREDIT_CODE); 351 } 352 else if (CANCEL_PAYMENT_REQUEST.equals(processType)) { 353 // on cancel, use DEBIT code 354 preq.setDebitCreditCodeForGLEntries(GL_DEBIT_CODE); 355 } 356 else if (MODIFY_PAYMENT_REQUEST.equals(processType)) { 357 // no encumbrances for modify 358 } 359 360 preq.setGenerateEncumbranceEntries(true); 361 for (Iterator iter = encumbrances.iterator(); iter.hasNext();) { 362 AccountingLine accountingLine = (AccountingLine) iter.next(); 363 preq.generateGeneralLedgerPendingEntries(accountingLine, sequenceHelper); 364 sequenceHelper.increment(); // increment for the next line 365 } 366 } 367 368 if (ObjectUtils.isNotNull(summaryAccounts) && !summaryAccounts.isEmpty()) { 369 LOG.debug("generateEntriesPaymentRequest() now book the actuals"); 370 preq.setGenerateEncumbranceEntries(false); 371 372 if (CREATE_PAYMENT_REQUEST.equals(processType) || MODIFY_PAYMENT_REQUEST.equals(processType)) { 373 // on create and modify, use DEBIT code 374 preq.setDebitCreditCodeForGLEntries(GL_DEBIT_CODE); 375 } 376 else if (CANCEL_PAYMENT_REQUEST.equals(processType)) { 377 // on cancel, use CREDIT code 378 preq.setDebitCreditCodeForGLEntries(GL_CREDIT_CODE); 379 } 380 381 for (Iterator iter = summaryAccounts.iterator(); iter.hasNext();) { 382 SummaryAccount summaryAccount = (SummaryAccount) iter.next(); 383 preq.generateGeneralLedgerPendingEntries(summaryAccount.getAccount(), sequenceHelper); 384 sequenceHelper.increment(); // increment for the next line 385 } 386 387 // generate offset accounts for use tax if it exists (useTaxContainers will be empty if not a use tax document) 388 List<UseTaxContainer> useTaxContainers = purapAccountingService.generateUseTaxAccount(preq); 389 for (UseTaxContainer useTaxContainer : useTaxContainers) { 390 PurApItemUseTax offset = useTaxContainer.getUseTax(); 391 List<SourceAccountingLine> accounts = useTaxContainer.getAccounts(); 392 for (SourceAccountingLine sourceAccountingLine : accounts) { 393 preq.generateGeneralLedgerPendingEntries(sourceAccountingLine, sequenceHelper, useTaxContainer.getUseTax()); 394 sequenceHelper.increment(); // increment for the next line 395 } 396 397 } 398 399 // Manually save preq summary accounts 400 if (MODIFY_PAYMENT_REQUEST.equals(processType)) { 401 //for modify, regenerate the summary from the doc 402 List<SummaryAccount> summaryAccountsForModify = purapAccountingService.generateSummaryAccountsWithNoZeroTotalsNoUseTax(preq); 403 saveAccountsPayableSummaryAccounts(summaryAccountsForModify, preq.getPurapDocumentIdentifier(), PurapDocTypeCodes.PAYMENT_REQUEST_DOCUMENT); 404 } 405 else { 406 //for create and cancel, use the summary accounts 407 saveAccountsPayableSummaryAccounts(summaryAccounts, preq.getPurapDocumentIdentifier(), PurapDocTypeCodes.PAYMENT_REQUEST_DOCUMENT); 408 } 409 410 // manually save cm account change tables (CAMS needs this) 411 if (CREATE_PAYMENT_REQUEST.equals(processType) || MODIFY_PAYMENT_REQUEST.equals(processType)) { 412 SpringContext.getBean(PurapAccountRevisionService.class).savePaymentRequestAccountRevisions(preq.getItems(), preq.getPostingYearFromPendingGLEntries(), preq.getPostingPeriodCodeFromPendingGLEntries()); 413 } 414 else if (CANCEL_PAYMENT_REQUEST.equals(processType)) { 415 SpringContext.getBean(PurapAccountRevisionService.class).cancelPaymentRequestAccountRevisions(preq.getItems(), preq.getPostingYearFromPendingGLEntries(), preq.getPostingPeriodCodeFromPendingGLEntries()); 416 } 417 } 418 419 420 // Manually save GL entries for Payment Request and encumbrances 421 saveGLEntries(preq.getGeneralLedgerPendingEntries()); 422 423 return success; 424 } 425 426 /** 427 * Creates the general ledger entries for Credit Memo actions. 428 * 429 * @param cm Credit Memo document to create entries 430 * @param isCancel Indicates if request is a cancel or create 431 * @return Boolean returned indicating whether entry creation succeeded 432 */ 433 protected boolean generateEntriesCreditMemo(VendorCreditMemoDocument cm, boolean isCancel) { 434 LOG.debug("generateEntriesCreditMemo() started"); 435 436 cm.setGeneralLedgerPendingEntries(new ArrayList()); 437 438 boolean success = true; 439 GeneralLedgerPendingEntrySequenceHelper sequenceHelper = new GeneralLedgerPendingEntrySequenceHelper(getNextAvailableSequence(cm.getDocumentNumber())); 440 441 if (!cm.isSourceVendor()) { 442 LOG.debug("generateEntriesCreditMemo() create encumbrance entries for CM against a PO or PREQ (not vendor)"); 443 PurchaseOrderDocument po = null; 444 if (cm.isSourceDocumentPurchaseOrder()) { 445 LOG.debug("generateEntriesCreditMemo() PO type"); 446 po = purchaseOrderService.getCurrentPurchaseOrder(cm.getPurchaseOrderIdentifier()); 447 } 448 else if (cm.isSourceDocumentPaymentRequest()) { 449 LOG.debug("generateEntriesCreditMemo() PREQ type"); 450 po = purchaseOrderService.getCurrentPurchaseOrder(cm.getPaymentRequestDocument().getPurchaseOrderIdentifier()); 451 } 452 453 // for CM cancel or create, do not book encumbrances if PO is CLOSED, but do update the amounts on the PO 454 List encumbrances = getCreditMemoEncumbrance(cm, po, isCancel); 455 if (!(PurapConstants.PurchaseOrderStatuses.CLOSED.equals(po.getStatusCode()))) { 456 if (encumbrances != null) { 457 cm.setGenerateEncumbranceEntries(true); 458 459 // even if generating encumbrance entries on cancel, call is the same because the method gets negative amounts 460 // from 461 // the map so Debits on negatives = a credit 462 cm.setDebitCreditCodeForGLEntries(GL_DEBIT_CODE); 463 464 for (Iterator iter = encumbrances.iterator(); iter.hasNext();) { 465 AccountingLine accountingLine = (AccountingLine) iter.next(); 466 if (accountingLine.getAmount().compareTo(ZERO) != 0) { 467 cm.generateGeneralLedgerPendingEntries(accountingLine, sequenceHelper); 468 sequenceHelper.increment(); // increment for the next line 469 } 470 } 471 } 472 } 473 } 474 475 List<SummaryAccount> summaryAccounts = purapAccountingService.generateSummaryAccountsWithNoZeroTotalsNoUseTax(cm); 476 if (summaryAccounts != null) { 477 LOG.debug("generateEntriesCreditMemo() now book the actuals"); 478 cm.setGenerateEncumbranceEntries(false); 479 480 if (!isCancel) { 481 // on create, use CREDIT code 482 cm.setDebitCreditCodeForGLEntries(GL_CREDIT_CODE); 483 } 484 else { 485 // on cancel, use DEBIT code 486 cm.setDebitCreditCodeForGLEntries(GL_DEBIT_CODE); 487 } 488 489 for (Iterator iter = summaryAccounts.iterator(); iter.hasNext();) { 490 SummaryAccount summaryAccount = (SummaryAccount) iter.next(); 491 cm.generateGeneralLedgerPendingEntries(summaryAccount.getAccount(), sequenceHelper); 492 sequenceHelper.increment(); // increment for the next line 493 } 494 // generate offset accounts for use tax if it exists (useTaxContainers will be empty if not a use tax document) 495 List<UseTaxContainer> useTaxContainers = purapAccountingService.generateUseTaxAccount(cm); 496 for (UseTaxContainer useTaxContainer : useTaxContainers) { 497 PurApItemUseTax offset = useTaxContainer.getUseTax(); 498 List<SourceAccountingLine> accounts = useTaxContainer.getAccounts(); 499 for (SourceAccountingLine sourceAccountingLine : accounts) { 500 cm.generateGeneralLedgerPendingEntries(sourceAccountingLine, sequenceHelper, useTaxContainer.getUseTax()); 501 sequenceHelper.increment(); // increment for the next line 502 } 503 504 } 505 506 // manually save cm account change tables (CAMS needs this) 507 if (!isCancel) { 508 SpringContext.getBean(PurapAccountRevisionService.class).saveCreditMemoAccountRevisions(cm.getItems(), cm.getPostingYearFromPendingGLEntries(), cm.getPostingPeriodCodeFromPendingGLEntries()); 509 } 510 else { 511 SpringContext.getBean(PurapAccountRevisionService.class).cancelCreditMemoAccountRevisions(cm.getItems(), cm.getPostingYearFromPendingGLEntries(), cm.getPostingPeriodCodeFromPendingGLEntries()); 512 } 513 } 514 515 saveGLEntries(cm.getGeneralLedgerPendingEntries()); 516 517 LOG.debug("generateEntriesCreditMemo() ended"); 518 return success; 519 } 520 521 /** 522 * @see org.kuali.kfs.module.purap.service.PurapGeneralLedgerService#generateEntriesApproveAmendPurchaseOrder(org.kuali.kfs.module.purap.document.PurchaseOrderDocument) 523 */ 524 public void generateEntriesApproveAmendPurchaseOrder(PurchaseOrderDocument po) { 525 LOG.debug("generateEntriesApproveAmendPurchaseOrder() started"); 526 527 // Set outstanding encumbered quantity/amount on items 528 for (Iterator items = po.getItems().iterator(); items.hasNext();) { 529 PurchaseOrderItem item = (PurchaseOrderItem) items.next(); 530 531 // if invoice fields are null (as would be for new items), set fields to zero 532 item.setItemInvoicedTotalAmount(item.getItemInvoicedTotalAmount() == null ? ZERO : item.getItemInvoicedTotalAmount()); 533 item.setItemInvoicedTotalQuantity(item.getItemInvoicedTotalQuantity() == null ? ZERO : item.getItemInvoicedTotalQuantity()); 534 535 if (!item.isItemActiveIndicator()) { 536 // set outstanding encumbrance amounts to zero for inactive items 537 item.setItemOutstandingEncumberedQuantity(ZERO); 538 item.setItemOutstandingEncumberedAmount(ZERO); 539 540 for (Iterator iter = item.getSourceAccountingLines().iterator(); iter.hasNext();) { 541 PurchaseOrderAccount account = (PurchaseOrderAccount) iter.next(); 542 account.setItemAccountOutstandingEncumbranceAmount(ZERO); 543 account.setAlternateAmountForGLEntryCreation(ZERO); 544 } 545 } 546 else { 547 // Set quantities 548 if (item.getItemQuantity() != null) { 549 item.setItemOutstandingEncumberedQuantity(item.getItemQuantity().subtract(item.getItemInvoicedTotalQuantity())); 550 } 551 else { 552 // if order qty is null, outstanding encumbered qty should be null 553 item.setItemOutstandingEncumberedQuantity(null); 554 } 555 556 // Set amount 557 if (item.getItemOutstandingEncumberedQuantity() != null) { 558 //do math as big decimal as doing it as a KualiDecimal will cause the item price to round to 2 digits 559 KualiDecimal itemEncumber = new KualiDecimal(item.getItemOutstandingEncumberedQuantity().bigDecimalValue().multiply(item.getItemUnitPrice())); 560 561 //add tax for encumbrance 562 KualiDecimal itemTaxAmount = item.getItemTaxAmount() == null ? ZERO : item.getItemTaxAmount(); 563 itemEncumber = itemEncumber.add(itemTaxAmount); 564 565 item.setItemOutstandingEncumberedAmount(itemEncumber); 566 } 567 else { 568 if (item.getItemUnitPrice() != null) { 569 item.setItemOutstandingEncumberedAmount(new KualiDecimal(item.getItemUnitPrice().subtract(item.getItemInvoicedTotalAmount().bigDecimalValue()))); 570 } 571 } 572 573 for (Iterator iter = item.getSourceAccountingLines().iterator(); iter.hasNext();) { 574 PurchaseOrderAccount account = (PurchaseOrderAccount) iter.next(); 575 BigDecimal percent = new BigDecimal(account.getAccountLinePercent().toString()); 576 percent = percent.divide(new BigDecimal("100"), 3, BigDecimal.ROUND_HALF_UP); 577 account.setItemAccountOutstandingEncumbranceAmount(item.getItemOutstandingEncumberedAmount().multiply(new KualiDecimal(percent))); 578 account.setAlternateAmountForGLEntryCreation(account.getItemAccountOutstandingEncumbranceAmount()); 579 } 580 } 581 } 582 583 PurchaseOrderDocument oldPO = purchaseOrderService.getCurrentPurchaseOrder(po.getPurapDocumentIdentifier()); 584 585 if (oldPO == null) { 586 throw new IllegalArgumentException("Current Purchase Order not found - poId = " + oldPO.getPurapDocumentIdentifier()); 587 } 588 589 List newAccounts = purapAccountingService.generateSummaryWithNoZeroTotalsUsingAlternateAmount(po.getItemsActiveOnly()); 590 List oldAccounts = purapAccountingService.generateSummaryWithNoZeroTotalsUsingAlternateAmount(oldPO.getItemsActiveOnlySetupAlternateAmount()); 591 592 Map combination = new HashMap(); 593 594 // Add amounts from the new PO 595 for (Iterator iter = newAccounts.iterator(); iter.hasNext();) { 596 SourceAccountingLine newAccount = (SourceAccountingLine) iter.next(); 597 combination.put(newAccount, newAccount.getAmount()); 598 } 599 600 LOG.info("generateEntriesApproveAmendPurchaseOrder() combination after the add"); 601 for (Iterator iter = combination.keySet().iterator(); iter.hasNext();) { 602 SourceAccountingLine element = (SourceAccountingLine) iter.next(); 603 LOG.info("generateEntriesApproveAmendPurchaseOrder() " + element + " = " + ((KualiDecimal) combination.get(element)).floatValue()); 604 } 605 606 // Subtract the amounts from the old PO 607 for (Iterator iter = oldAccounts.iterator(); iter.hasNext();) { 608 SourceAccountingLine oldAccount = (SourceAccountingLine) iter.next(); 609 if (combination.containsKey(oldAccount)) { 610 KualiDecimal amount = (KualiDecimal) combination.get(oldAccount); 611 amount = amount.subtract(oldAccount.getAmount()); 612 combination.put(oldAccount, amount); 613 } 614 else { 615 combination.put(oldAccount, ZERO.subtract(oldAccount.getAmount())); 616 } 617 } 618 619 LOG.debug("generateEntriesApproveAmendPurchaseOrder() combination after the subtract"); 620 for (Iterator iter = combination.keySet().iterator(); iter.hasNext();) { 621 SourceAccountingLine element = (SourceAccountingLine) iter.next(); 622 LOG.info("generateEntriesApproveAmendPurchaseOrder() " + element + " = " + ((KualiDecimal) combination.get(element)).floatValue()); 623 } 624 625 List<SourceAccountingLine> encumbranceAccounts = new ArrayList(); 626 for (Iterator iter = combination.keySet().iterator(); iter.hasNext();) { 627 SourceAccountingLine account = (SourceAccountingLine) iter.next(); 628 KualiDecimal amount = (KualiDecimal) combination.get(account); 629 if (ZERO.compareTo(amount) != 0) { 630 account.setAmount(amount); 631 encumbranceAccounts.add(account); 632 } 633 } 634 635 po.setGlOnlySourceAccountingLines(encumbranceAccounts); 636 generalLedgerPendingEntryService.generateGeneralLedgerPendingEntries(po); 637 saveGLEntries(po.getGeneralLedgerPendingEntries()); 638 LOG.debug("generateEntriesApproveAmendPo() gl entries created; exit method"); 639 } 640 641 /** 642 * @see org.kuali.kfs.module.purap.service.PurapGeneralLedgerService#generateEntriesClosePurchaseOrder(org.kuali.kfs.module.purap.document.PurchaseOrderDocument) 643 */ 644 public void generateEntriesClosePurchaseOrder(PurchaseOrderDocument po) { 645 LOG.debug("generateEntriesClosePurchaseOrder() started"); 646 647 // Set outstanding encumbered quantity/amount on items 648 for (Iterator items = po.getItems().iterator(); items.hasNext();) { 649 PurchaseOrderItem item = (PurchaseOrderItem) items.next(); 650 651 String logItmNbr = "Item # " + item.getItemLineNumber(); 652 653 if (!item.isItemActiveIndicator()) { 654 continue; 655 } 656 657 KualiDecimal itemAmount = null; 658 LOG.debug("generateEntriesClosePurchaseOrder() " + logItmNbr + " Calculate based on amounts"); 659 itemAmount = item.getItemOutstandingEncumberedAmount() == null ? ZERO : item.getItemOutstandingEncumberedAmount(); 660 661 KualiDecimal accountTotal = ZERO; 662 PurchaseOrderAccount lastAccount = null; 663 if (itemAmount.compareTo(ZERO) != 0) { 664 // Sort accounts 665 Collections.sort((List) item.getSourceAccountingLines()); 666 667 for (Iterator iterAcct = item.getSourceAccountingLines().iterator(); iterAcct.hasNext();) { 668 PurchaseOrderAccount acct = (PurchaseOrderAccount) iterAcct.next(); 669 if (!acct.isEmpty()) { 670 KualiDecimal acctAmount = itemAmount.multiply(new KualiDecimal(acct.getAccountLinePercent().toString())).divide(PurapConstants.HUNDRED); 671 accountTotal = accountTotal.add(acctAmount); 672 acct.setAlternateAmountForGLEntryCreation(acctAmount); 673 lastAccount = acct; 674 } 675 } 676 677 // account for rounding by adjusting last account as needed 678 if (lastAccount != null) { 679 KualiDecimal difference = itemAmount.subtract(accountTotal); 680 LOG.debug("generateEntriesClosePurchaseOrder() difference: " + logItmNbr + " " + difference); 681 682 KualiDecimal amount = lastAccount.getAlternateAmountForGLEntryCreation(); 683 if (ObjectUtils.isNotNull(amount)) { 684 lastAccount.setAlternateAmountForGLEntryCreation(amount.add(difference)); 685 } 686 else { 687 lastAccount.setAlternateAmountForGLEntryCreation(difference); 688 } 689 } 690 691 } 692 }// endfor 693 694 po.setGlOnlySourceAccountingLines(purapAccountingService.generateSummaryWithNoZeroTotalsUsingAlternateAmount(po.getItemsActiveOnly())); 695 if (shouldGenerateGLPEForPurchaseOrder(po)) { 696 generalLedgerPendingEntryService.generateGeneralLedgerPendingEntries(po); 697 saveGLEntries(po.getGeneralLedgerPendingEntries()); 698 LOG.debug("generateEntriesClosePurchaseOrder() gl entries created; exit method"); 699 } 700 LOG.debug("generateEntriesClosePurchaseOrder() no gl entries created because the amount is 0; exit method"); 701 } 702 703 /** 704 * We should not generate general ledger pending entries for Purchase Order Close Document and 705 * Purchase Order Reopen Document with $0 amount. 706 * 707 * @param po 708 * @return 709 */ 710 protected boolean shouldGenerateGLPEForPurchaseOrder(PurchaseOrderDocument po) { 711 for (SourceAccountingLine acct : (List<SourceAccountingLine>)po.getSourceAccountingLines()) { 712 if (acct.getAmount().abs().compareTo(new KualiDecimal(0)) > 0) { 713 return true; 714 } 715 } 716 return false; 717 } 718 719 /** 720 * @see org.kuali.kfs.module.purap.service.PurapGeneralLedgerService#generateEntriesReopenPurchaseOrder(org.kuali.kfs.module.purap.document.PurchaseOrderDocument) 721 */ 722 public void generateEntriesReopenPurchaseOrder(PurchaseOrderDocument po) { 723 LOG.debug("generateEntriesReopenPurchaseOrder() started"); 724 725 // Set outstanding encumbered quantity/amount on items 726 for (Iterator items = po.getItems().iterator(); items.hasNext();) { 727 PurchaseOrderItem item = (PurchaseOrderItem) items.next(); 728 729 String logItmNbr = "Item # " + item.getItemLineNumber(); 730 731 if (!item.isItemActiveIndicator()) { 732 continue; 733 } 734 735 KualiDecimal itemAmount = null; 736 if (item.getItemType().isAmountBasedGeneralLedgerIndicator()) { 737 LOG.debug("generateEntriesReopenPurchaseOrder() " + logItmNbr + " Calculate based on amounts"); 738 itemAmount = item.getItemOutstandingEncumberedAmount() == null ? ZERO : item.getItemOutstandingEncumberedAmount(); 739 } 740 else { 741 LOG.debug("generateEntriesReopenPurchaseOrder() " + logItmNbr + " Calculate based on quantities"); 742 //do math as big decimal as doing it as a KualiDecimal will cause the item price to round to 2 digits 743 itemAmount = new KualiDecimal(item.getItemOutstandingEncumberedQuantity().bigDecimalValue().multiply(item.getItemUnitPrice())); 744 } 745 746 KualiDecimal accountTotal = ZERO; 747 PurchaseOrderAccount lastAccount = null; 748 if (itemAmount.compareTo(ZERO) != 0) { 749 // Sort accounts 750 Collections.sort((List) item.getSourceAccountingLines()); 751 752 for (Iterator iterAcct = item.getSourceAccountingLines().iterator(); iterAcct.hasNext();) { 753 PurchaseOrderAccount acct = (PurchaseOrderAccount) iterAcct.next(); 754 if (!acct.isEmpty()) { 755 KualiDecimal acctAmount = itemAmount.multiply(new KualiDecimal(acct.getAccountLinePercent().toString())).divide(PurapConstants.HUNDRED); 756 accountTotal = accountTotal.add(acctAmount); 757 acct.setAlternateAmountForGLEntryCreation(acctAmount); 758 lastAccount = acct; 759 } 760 } 761 762 // account for rounding by adjusting last account as needed 763 if (lastAccount != null) { 764 KualiDecimal difference = itemAmount.subtract(accountTotal); 765 LOG.debug("generateEntriesReopenPurchaseOrder() difference: " + logItmNbr + " " + difference); 766 767 KualiDecimal amount = lastAccount.getAlternateAmountForGLEntryCreation(); 768 if (ObjectUtils.isNotNull(amount)) { 769 lastAccount.setAlternateAmountForGLEntryCreation(amount.add(difference)); 770 } 771 else { 772 lastAccount.setAlternateAmountForGLEntryCreation(difference); 773 } 774 } 775 776 } 777 }// endfor 778 779 po.setGlOnlySourceAccountingLines(purapAccountingService.generateSummaryWithNoZeroTotalsUsingAlternateAmount(po.getItemsActiveOnly())); 780 if (shouldGenerateGLPEForPurchaseOrder(po)) { 781 generalLedgerPendingEntryService.generateGeneralLedgerPendingEntries(po); 782 saveGLEntries(po.getGeneralLedgerPendingEntries()); 783 LOG.debug("generateEntriesReopenPurchaseOrder() gl entries created; exit method"); 784 } 785 LOG.debug("generateEntriesReopenPurchaseOrder() no gl entries created because the amount is 0; exit method"); 786 } 787 788 /** 789 * @see org.kuali.kfs.module.purap.service.PurapGeneralLedgerService#generateEntriesVoidPurchaseOrder(org.kuali.kfs.module.purap.document.PurchaseOrderDocument) 790 */ 791 public void generateEntriesVoidPurchaseOrder(PurchaseOrderDocument po) { 792 LOG.debug("generateEntriesVoidPurchaseOrder() started"); 793 794 // Set outstanding encumbered quantity/amount on items 795 for (Iterator items = po.getItems().iterator(); items.hasNext();) { 796 PurchaseOrderItem item = (PurchaseOrderItem) items.next(); 797 798 String logItmNbr = "Item # " + item.getItemLineNumber(); 799 800 if (!item.isItemActiveIndicator()) { 801 continue; 802 } 803 804 //just use the outstanding amount as recalculating here, particularly the item tax will cause 805 //amounts to be over or under encumbered and the remaining encumbered amount should be unencumbered during a close 806 LOG.debug("generateEntriesVoidPurchaseOrder() " + logItmNbr + " Calculate based on amounts"); 807 KualiDecimal itemAmount = item.getItemOutstandingEncumberedAmount() == null ? ZERO : item.getItemOutstandingEncumberedAmount(); 808 809 KualiDecimal accountTotal = ZERO; 810 PurchaseOrderAccount lastAccount = null; 811 if (itemAmount.compareTo(ZERO) != 0) { 812 // Sort accounts 813 Collections.sort((List) item.getSourceAccountingLines()); 814 815 for (Iterator iterAcct = item.getSourceAccountingLines().iterator(); iterAcct.hasNext();) { 816 PurchaseOrderAccount acct = (PurchaseOrderAccount) iterAcct.next(); 817 if (!acct.isEmpty()) { 818 KualiDecimal acctAmount = itemAmount.multiply(new KualiDecimal(acct.getAccountLinePercent().toString())).divide(PurapConstants.HUNDRED); 819 accountTotal = accountTotal.add(acctAmount); 820 acct.setAlternateAmountForGLEntryCreation(acctAmount); 821 lastAccount = acct; 822 } 823 } 824 825 // account for rounding by adjusting last account as needed 826 if (lastAccount != null) { 827 KualiDecimal difference = itemAmount.subtract(accountTotal); 828 LOG.debug("generateEntriesVoidPurchaseOrder() difference: " + logItmNbr + " " + difference); 829 830 KualiDecimal amount = lastAccount.getAlternateAmountForGLEntryCreation(); 831 if (ObjectUtils.isNotNull(amount)) { 832 lastAccount.setAlternateAmountForGLEntryCreation(amount.add(difference)); 833 } 834 else { 835 lastAccount.setAlternateAmountForGLEntryCreation(difference); 836 } 837 } 838 839 } 840 }// endfor 841 842 po.setGlOnlySourceAccountingLines(purapAccountingService.generateSummaryWithNoZeroTotalsUsingAlternateAmount(po.getItemsActiveOnly())); 843 generalLedgerPendingEntryService.generateGeneralLedgerPendingEntries(po); 844 saveGLEntries(po.getGeneralLedgerPendingEntries()); 845 LOG.debug("generateEntriesVoidPurchaseOrder() gl entries created; exit method"); 846 } 847 848 /** 849 * Relieve the Encumbrance on a PO based on values in a PREQ. This is to be called when a PREQ is created. Note: This modifies 850 * the encumbrance values on the PO and saves the PO 851 * 852 * @param preq PREQ for invoice 853 * @return List of accounting lines to use to create the pending general ledger entries 854 */ 855 protected List<SourceAccountingLine> relieveEncumbrance(PaymentRequestDocument preq) { 856 LOG.debug("relieveEncumbrance() started"); 857 858 Map encumbranceAccountMap = new HashMap(); 859 PurchaseOrderDocument po = purchaseOrderService.getCurrentPurchaseOrder(preq.getPurchaseOrderIdentifier()); 860 861 // Get each item one by one 862 for (Iterator items = preq.getItems().iterator(); items.hasNext();) { 863 PaymentRequestItem preqItem = (PaymentRequestItem) items.next(); 864 PurchaseOrderItem poItem = getPoItem(po, preqItem.getItemLineNumber(), preqItem.getItemType()); 865 866 boolean takeAll = false; // Set this true if we relieve the entire encumbrance 867 KualiDecimal itemDisEncumber = null; // Amount to disencumber for this item 868 869 String logItmNbr = "Item # " + preqItem.getItemLineNumber(); 870 LOG.debug("relieveEncumbrance() " + logItmNbr); 871 872 // If there isn't a PO item or the extended price is 0, we don't need encumbrances 873 if (poItem == null) { 874 LOG.debug("relieveEncumbrance() " + logItmNbr + " No encumbrances required because po item is null"); 875 } 876 else { 877 final KualiDecimal preqItemTotalAmount = (preqItem.getTotalAmount() == null) ? KualiDecimal.ZERO : preqItem.getTotalAmount(); 878 if (ZERO.compareTo(preqItemTotalAmount) == 0) { 879 /* 880 * This is a specialized case where PREQ item being processed must adjust the PO item's outstanding encumbered 881 * quantity. This kind of scenario is mostly seen on warranty type items. The following must be true to do this: 882 * PREQ item Extended Price must be ZERO, PREQ item invoice quantity must be not empty and not ZERO, and PO item 883 * is quantity based PO item unit cost is ZERO 884 */ 885 LOG.debug("relieveEncumbrance() " + logItmNbr + " No GL encumbrances required because extended price is ZERO"); 886 if ((poItem.getItemQuantity() != null) && ((BigDecimal.ZERO.compareTo(poItem.getItemUnitPrice())) == 0)) { 887 // po has order quantity and unit price is ZERO... reduce outstanding encumbered quantity 888 LOG.debug("relieveEncumbrance() " + logItmNbr + " Calculate po oustanding encumbrance"); 889 890 // Do encumbrance calculations based on quantity 891 if ((preqItem.getItemQuantity() != null) && ((ZERO.compareTo(preqItem.getItemQuantity())) != 0)) { 892 KualiDecimal invoiceQuantity = preqItem.getItemQuantity(); 893 KualiDecimal outstandingEncumberedQuantity = poItem.getItemOutstandingEncumberedQuantity() == null ? ZERO : poItem.getItemOutstandingEncumberedQuantity(); 894 895 KualiDecimal encumbranceQuantity; 896 if (invoiceQuantity.compareTo(outstandingEncumberedQuantity) > 0) { 897 // We bought more than the quantity on the PO 898 LOG.debug("relieveEncumbrance() " + logItmNbr + " we bought more than the qty on the PO"); 899 encumbranceQuantity = outstandingEncumberedQuantity; 900 poItem.setItemOutstandingEncumberedQuantity(ZERO); 901 } 902 else { 903 encumbranceQuantity = invoiceQuantity; 904 poItem.setItemOutstandingEncumberedQuantity(outstandingEncumberedQuantity.subtract(encumbranceQuantity)); 905 LOG.debug("relieveEncumbrance() " + logItmNbr + " adjusting oustanding encunbrance qty - encumbranceQty " + encumbranceQuantity + " outstandingEncumberedQty " + poItem.getItemOutstandingEncumberedQuantity()); 906 } 907 908 if (poItem.getItemInvoicedTotalQuantity() == null) { 909 poItem.setItemInvoicedTotalQuantity(invoiceQuantity); 910 } 911 else { 912 poItem.setItemInvoicedTotalQuantity(poItem.getItemInvoicedTotalQuantity().add(invoiceQuantity)); 913 } 914 } 915 } 916 917 918 } 919 else { 920 LOG.debug("relieveEncumbrance() " + logItmNbr + " Calculate encumbrance GL entries"); 921 922 // Do we calculate the encumbrance amount based on quantity or amount? 923 if (poItem.getItemType().isQuantityBasedGeneralLedgerIndicator()) { 924 LOG.debug("relieveEncumbrance() " + logItmNbr + " Calculate encumbrance based on quantity"); 925 926 // Do encumbrance calculations based on quantity 927 KualiDecimal invoiceQuantity = preqItem.getItemQuantity() == null ? ZERO : preqItem.getItemQuantity(); 928 KualiDecimal outstandingEncumberedQuantity = poItem.getItemOutstandingEncumberedQuantity() == null ? ZERO : poItem.getItemOutstandingEncumberedQuantity(); 929 930 KualiDecimal encumbranceQuantity; 931 932 if (invoiceQuantity.compareTo(outstandingEncumberedQuantity) > 0) { 933 // We bought more than the quantity on the PO 934 LOG.debug("relieveEncumbrance() " + logItmNbr + " we bought more than the qty on the PO"); 935 encumbranceQuantity = outstandingEncumberedQuantity; 936 poItem.setItemOutstandingEncumberedQuantity(ZERO); 937 takeAll = true; 938 } 939 else { 940 encumbranceQuantity = invoiceQuantity; 941 poItem.setItemOutstandingEncumberedQuantity(outstandingEncumberedQuantity.subtract(encumbranceQuantity)); 942 if (ZERO.compareTo(poItem.getItemOutstandingEncumberedQuantity()) == 0) { 943 takeAll = true; 944 } 945 LOG.debug("relieveEncumbrance() " + logItmNbr + " encumbranceQty " + encumbranceQuantity + " outstandingEncumberedQty " + poItem.getItemOutstandingEncumberedQuantity()); 946 } 947 948 if (poItem.getItemInvoicedTotalQuantity() == null) { 949 poItem.setItemInvoicedTotalQuantity(invoiceQuantity); 950 } 951 else { 952 poItem.setItemInvoicedTotalQuantity(poItem.getItemInvoicedTotalQuantity().add(invoiceQuantity)); 953 } 954 955 itemDisEncumber = new KualiDecimal(encumbranceQuantity.bigDecimalValue().multiply(poItem.getItemUnitPrice())); 956 957 //add tax for encumbrance 958 KualiDecimal itemTaxAmount = poItem.getItemTaxAmount() == null ? ZERO : poItem.getItemTaxAmount(); 959 KualiDecimal encumbranceTaxAmount = encumbranceQuantity.divide(poItem.getItemQuantity()).multiply(itemTaxAmount); 960 itemDisEncumber = itemDisEncumber.add(encumbranceTaxAmount); 961 } 962 else { 963 LOG.debug("relieveEncumbrance() " + logItmNbr + " Calculate encumbrance based on amount"); 964 965 // Do encumbrance calculations based on amount only 966 if ((poItem.getItemOutstandingEncumberedAmount().bigDecimalValue().signum() == -1) && (preqItemTotalAmount.bigDecimalValue().signum() == -1)) { 967 LOG.debug("relieveEncumbrance() " + logItmNbr + " Outstanding Encumbered amount is negative: " + poItem.getItemOutstandingEncumberedAmount()); 968 if (preqItemTotalAmount.compareTo(poItem.getItemOutstandingEncumberedAmount()) >= 0) { 969 // extended price is equal to or greater than outstanding encumbered 970 itemDisEncumber = preqItemTotalAmount; 971 } 972 else { 973 // extended price is less than outstanding encumbered 974 takeAll = true; 975 itemDisEncumber = poItem.getItemOutstandingEncumberedAmount(); 976 } 977 } 978 else { 979 LOG.debug("relieveEncumbrance() " + logItmNbr + " Outstanding Encumbered amount is positive or ZERO: " + poItem.getItemOutstandingEncumberedAmount()); 980 if (poItem.getItemOutstandingEncumberedAmount().compareTo(preqItemTotalAmount) >= 0) { 981 // outstanding amount is equal to or greater than extended price 982 itemDisEncumber = preqItemTotalAmount; 983 } 984 else { 985 // outstanding amount is less than extended price 986 takeAll = true; 987 itemDisEncumber = poItem.getItemOutstandingEncumberedAmount(); 988 } 989 } 990 } 991 992 LOG.debug("relieveEncumbrance() " + logItmNbr + " Amount to disencumber: " + itemDisEncumber); 993 994 KualiDecimal newOutstandingEncumberedAmount = poItem.getItemOutstandingEncumberedAmount().subtract(itemDisEncumber); 995 LOG.debug("relieveEncumbrance() " + logItmNbr + " New Outstanding Encumbered amount is : " + newOutstandingEncumberedAmount); 996 poItem.setItemOutstandingEncumberedAmount(newOutstandingEncumberedAmount); 997 998 KualiDecimal newInvoicedTotalAmount = poItem.getItemInvoicedTotalAmount().add(preqItemTotalAmount); 999 LOG.debug("relieveEncumbrance() " + logItmNbr + " New Invoiced Total Amount is: " + newInvoicedTotalAmount); 1000 poItem.setItemInvoicedTotalAmount(newInvoicedTotalAmount); 1001 1002 // Sort accounts 1003 Collections.sort((List) poItem.getSourceAccountingLines()); 1004 1005 // make the list of accounts for the disencumbrance entry 1006 PurchaseOrderAccount lastAccount = null; 1007 KualiDecimal accountTotal = ZERO; 1008 for (Iterator accountIter = poItem.getSourceAccountingLines().iterator(); accountIter.hasNext();) { 1009 PurchaseOrderAccount account = (PurchaseOrderAccount) accountIter.next(); 1010 if (!account.isEmpty()) { 1011 KualiDecimal encumbranceAmount = null; 1012 SourceAccountingLine acctString = account.generateSourceAccountingLine(); 1013 if (takeAll) { 1014 // fully paid; remove remaining encumbrance 1015 encumbranceAmount = account.getItemAccountOutstandingEncumbranceAmount(); 1016 account.setItemAccountOutstandingEncumbranceAmount(ZERO); 1017 LOG.debug("relieveEncumbrance() " + logItmNbr + " take all"); 1018 } 1019 else { 1020 // amount = item disencumber * account percent / 100 1021 encumbranceAmount = itemDisEncumber.multiply(new KualiDecimal(account.getAccountLinePercent().toString())).divide(HUNDRED); 1022 1023 account.setItemAccountOutstandingEncumbranceAmount(account.getItemAccountOutstandingEncumbranceAmount().subtract(encumbranceAmount)); 1024 1025 // For rounding check at the end 1026 accountTotal = accountTotal.add(encumbranceAmount); 1027 1028 // If we are zeroing out the encumbrance, we don't need to adjust for rounding 1029 if (!takeAll) { 1030 lastAccount = account; 1031 } 1032 } 1033 1034 LOG.debug("relieveEncumbrance() " + logItmNbr + " " + acctString + " = " + encumbranceAmount); 1035 if (ObjectUtils.isNull(encumbranceAccountMap.get(acctString))) { 1036 encumbranceAccountMap.put(acctString, encumbranceAmount); 1037 } 1038 else { 1039 KualiDecimal amt = (KualiDecimal) encumbranceAccountMap.get(acctString); 1040 encumbranceAccountMap.put(acctString, amt.add(encumbranceAmount)); 1041 } 1042 1043 } 1044 } 1045 1046 // account for rounding by adjusting last account as needed 1047 if (lastAccount != null) { 1048 KualiDecimal difference = itemDisEncumber.subtract(accountTotal); 1049 LOG.debug("relieveEncumbrance() difference: " + logItmNbr + " " + difference); 1050 1051 SourceAccountingLine acctString = lastAccount.generateSourceAccountingLine(); 1052 KualiDecimal amount = (KualiDecimal) encumbranceAccountMap.get(acctString); 1053 if (ObjectUtils.isNull(amount)) { 1054 encumbranceAccountMap.put(acctString, difference); 1055 } 1056 else { 1057 encumbranceAccountMap.put(acctString, amount.add(difference)); 1058 } 1059 1060 lastAccount.setItemAccountOutstandingEncumbranceAmount(lastAccount.getItemAccountOutstandingEncumbranceAmount().subtract(difference)); 1061 } 1062 } 1063 } 1064 }// endfor 1065 1066 List<SourceAccountingLine> encumbranceAccounts = new ArrayList(); 1067 for (Iterator iter = encumbranceAccountMap.keySet().iterator(); iter.hasNext();) { 1068 SourceAccountingLine acctString = (SourceAccountingLine) iter.next(); 1069 KualiDecimal amount = (KualiDecimal) encumbranceAccountMap.get(acctString); 1070 if (amount.doubleValue() != 0) { 1071 acctString.setAmount(amount); 1072 encumbranceAccounts.add(acctString); 1073 } 1074 } 1075 1076 SpringContext.getBean(BusinessObjectService.class).save(po); 1077 return encumbranceAccounts; 1078 } 1079 1080 /** 1081 * Re-encumber the Encumbrance on a PO based on values in a PREQ. This is used when a PREQ is cancelled. Note: This modifies the 1082 * encumbrance values on the PO and saves the PO 1083 * 1084 * @param preq PREQ for invoice 1085 * @return List of accounting lines to use to create the pending general ledger entries 1086 */ 1087 protected List<SourceAccountingLine> reencumberEncumbrance(PaymentRequestDocument preq) { 1088 LOG.debug("reencumberEncumbrance() started"); 1089 1090 PurchaseOrderDocument po = purchaseOrderService.getCurrentPurchaseOrder(preq.getPurchaseOrderIdentifier()); 1091 Map encumbranceAccountMap = new HashMap(); 1092 1093 // Get each item one by one 1094 for (Iterator items = preq.getItems().iterator(); items.hasNext();) { 1095 PaymentRequestItem payRequestItem = (PaymentRequestItem) items.next(); 1096 PurchaseOrderItem poItem = getPoItem(po, payRequestItem.getItemLineNumber(), payRequestItem.getItemType()); 1097 1098 KualiDecimal itemReEncumber = null; // Amount to reencumber for this item 1099 1100 String logItmNbr = "Item # " + payRequestItem.getItemLineNumber(); 1101 LOG.debug("reencumberEncumbrance() " + logItmNbr); 1102 1103 // If there isn't a PO item or the total amount is 0, we don't need encumbrances 1104 final KualiDecimal preqItemTotalAmount = (payRequestItem.getTotalAmount() == null) ? KualiDecimal.ZERO : payRequestItem.getTotalAmount(); 1105 if ((poItem == null) || (preqItemTotalAmount.doubleValue() == 0)) { 1106 LOG.debug("reencumberEncumbrance() " + logItmNbr + " No encumbrances required"); 1107 } 1108 else { 1109 LOG.debug("reencumberEncumbrance() " + logItmNbr + " Calculate encumbrance GL entries"); 1110 1111 // Do we calculate the encumbrance amount based on quantity or amount? 1112 if (poItem.getItemType().isQuantityBasedGeneralLedgerIndicator()) { 1113 LOG.debug("reencumberEncumbrance() " + logItmNbr + " Calculate encumbrance based on quantity"); 1114 1115 // Do disencumbrance calculations based on quantity 1116 KualiDecimal preqQuantity = payRequestItem.getItemQuantity() == null ? ZERO : payRequestItem.getItemQuantity(); 1117 KualiDecimal outstandingEncumberedQuantity = poItem.getItemOutstandingEncumberedQuantity() == null ? ZERO : poItem.getItemOutstandingEncumberedQuantity(); 1118 KualiDecimal invoicedTotal = poItem.getItemInvoicedTotalQuantity() == null ? ZERO : poItem.getItemInvoicedTotalQuantity(); 1119 1120 poItem.setItemInvoicedTotalQuantity(invoicedTotal.subtract(preqQuantity)); 1121 poItem.setItemOutstandingEncumberedQuantity(outstandingEncumberedQuantity.add(preqQuantity)); 1122 1123 //do math as big decimal as doing it as a KualiDecimal will cause the item price to round to 2 digits 1124 itemReEncumber = new KualiDecimal(preqQuantity.bigDecimalValue().multiply(poItem.getItemUnitPrice())); 1125 1126 //add tax for encumbrance 1127 KualiDecimal itemTaxAmount = poItem.getItemTaxAmount() == null ? ZERO : poItem.getItemTaxAmount(); 1128 KualiDecimal encumbranceTaxAmount = preqQuantity.divide(poItem.getItemQuantity()).multiply(itemTaxAmount); 1129 itemReEncumber = itemReEncumber.add(encumbranceTaxAmount); 1130 1131 } 1132 else { 1133 LOG.debug("reencumberEncumbrance() " + logItmNbr + " Calculate encumbrance based on amount"); 1134 1135 itemReEncumber = preqItemTotalAmount; 1136 // if re-encumber amount is more than original PO ordered amount... do not exceed ordered amount 1137 // this prevents negative encumbrance 1138 if ((poItem.getTotalAmount() != null) && (poItem.getTotalAmount().bigDecimalValue().signum() < 0)) { 1139 // po item extended cost is negative 1140 if ((poItem.getTotalAmount().compareTo(itemReEncumber)) > 0) { 1141 itemReEncumber = poItem.getTotalAmount(); 1142 } 1143 } 1144 else if ((poItem.getTotalAmount() != null) && (poItem.getTotalAmount().bigDecimalValue().signum() >= 0)) { 1145 // po item extended cost is positive 1146 if ((poItem.getTotalAmount().compareTo(itemReEncumber)) < 0) { 1147 itemReEncumber = poItem.getTotalAmount(); 1148 } 1149 } 1150 } 1151 1152 LOG.debug("reencumberEncumbrance() " + logItmNbr + " Amount to reencumber: " + itemReEncumber); 1153 1154 KualiDecimal outstandingEncumberedAmount = poItem.getItemOutstandingEncumberedAmount() == null ? ZERO : poItem.getItemOutstandingEncumberedAmount(); 1155 LOG.debug("reencumberEncumbrance() " + logItmNbr + " PO Item Outstanding Encumbrance Amount set to: " + outstandingEncumberedAmount); 1156 KualiDecimal newOutstandingEncumberedAmount = outstandingEncumberedAmount.add(itemReEncumber); 1157 LOG.debug("reencumberEncumbrance() " + logItmNbr + " New PO Item Outstanding Encumbrance Amount to set: " + newOutstandingEncumberedAmount); 1158 poItem.setItemOutstandingEncumberedAmount(newOutstandingEncumberedAmount); 1159 1160 KualiDecimal invoicedTotalAmount = poItem.getItemInvoicedTotalAmount() == null ? ZERO : poItem.getItemInvoicedTotalAmount(); 1161 LOG.debug("reencumberEncumbrance() " + logItmNbr + " PO Item Invoiced Total Amount set to: " + invoicedTotalAmount); 1162 KualiDecimal newInvoicedTotalAmount = invoicedTotalAmount.subtract(preqItemTotalAmount); 1163 LOG.debug("reencumberEncumbrance() " + logItmNbr + " New PO Item Invoiced Total Amount to set: " + newInvoicedTotalAmount); 1164 poItem.setItemInvoicedTotalAmount(newInvoicedTotalAmount); 1165 1166 // make the list of accounts for the reencumbrance entry 1167 PurchaseOrderAccount lastAccount = null; 1168 KualiDecimal accountTotal = ZERO; 1169 1170 // Sort accounts 1171 Collections.sort((List) poItem.getSourceAccountingLines()); 1172 1173 for (Iterator accountIter = poItem.getSourceAccountingLines().iterator(); accountIter.hasNext();) { 1174 PurchaseOrderAccount account = (PurchaseOrderAccount) accountIter.next(); 1175 if (!account.isEmpty()) { 1176 SourceAccountingLine acctString = account.generateSourceAccountingLine(); 1177 1178 // amount = item reencumber * account percent / 100 1179 KualiDecimal reencumbranceAmount = itemReEncumber.multiply(new KualiDecimal(account.getAccountLinePercent().toString())).divide(HUNDRED); 1180 1181 account.setItemAccountOutstandingEncumbranceAmount(account.getItemAccountOutstandingEncumbranceAmount().add(reencumbranceAmount)); 1182 1183 // For rounding check at the end 1184 accountTotal = accountTotal.add(reencumbranceAmount); 1185 1186 lastAccount = account; 1187 1188 LOG.debug("reencumberEncumbrance() " + logItmNbr + " " + acctString + " = " + reencumbranceAmount); 1189 if (encumbranceAccountMap.containsKey(acctString)) { 1190 KualiDecimal currentAmount = (KualiDecimal) encumbranceAccountMap.get(acctString); 1191 encumbranceAccountMap.put(acctString, reencumbranceAmount.add(currentAmount)); 1192 } 1193 else { 1194 encumbranceAccountMap.put(acctString, reencumbranceAmount); 1195 } 1196 } 1197 } 1198 1199 // account for rounding by adjusting last account as needed 1200 if (lastAccount != null) { 1201 KualiDecimal difference = itemReEncumber.subtract(accountTotal); 1202 LOG.debug("reencumberEncumbrance() difference: " + logItmNbr + " " + difference); 1203 1204 SourceAccountingLine acctString = lastAccount.generateSourceAccountingLine(); 1205 KualiDecimal amount = (KualiDecimal) encumbranceAccountMap.get(acctString); 1206 if (amount == null) { 1207 encumbranceAccountMap.put(acctString, difference); 1208 } 1209 else { 1210 encumbranceAccountMap.put(acctString, amount.add(difference)); 1211 } 1212 lastAccount.setItemAccountOutstandingEncumbranceAmount(lastAccount.getItemAccountOutstandingEncumbranceAmount().add(difference)); 1213 } 1214 } 1215 } 1216 1217 SpringContext.getBean(PurapService.class).saveDocumentNoValidation(po); 1218 1219 List<SourceAccountingLine> encumbranceAccounts = new ArrayList<SourceAccountingLine>(); 1220 for (Iterator<SourceAccountingLine> iter = encumbranceAccountMap.keySet().iterator(); iter.hasNext();) { 1221 SourceAccountingLine acctString = (SourceAccountingLine) iter.next(); 1222 KualiDecimal amount = (KualiDecimal) encumbranceAccountMap.get(acctString); 1223 if (amount.doubleValue() != 0) { 1224 acctString.setAmount(amount); 1225 encumbranceAccounts.add(acctString); 1226 } 1227 } 1228 1229 return encumbranceAccounts; 1230 } 1231 1232 1233 /** 1234 * Re-encumber the Encumbrance on a PO based on values in a PREQ. This is used when a PREQ is cancelled. Note: This modifies the 1235 * encumbrance values on the PO and saves the PO 1236 * 1237 * @param cm Credit Memo document 1238 * @param po Purchase Order document modify encumbrances 1239 * @return List of accounting lines to use to create the pending general ledger entries 1240 */ 1241 protected List<SourceAccountingLine> getCreditMemoEncumbrance(VendorCreditMemoDocument cm, PurchaseOrderDocument po, boolean cancel) { 1242 LOG.debug("getCreditMemoEncumbrance() started"); 1243 1244 if (ObjectUtils.isNull(po)) { 1245 return null; 1246 } 1247 1248 if (cancel) { 1249 LOG.debug("getCreditMemoEncumbrance() Receiving items back from vendor (cancelled CM)"); 1250 } 1251 else { 1252 LOG.debug("getCreditMemoEncumbrance() Returning items to vendor"); 1253 } 1254 1255 Map encumbranceAccountMap = new HashMap(); 1256 1257 // Get each item one by one 1258 for (Iterator items = cm.getItems().iterator(); items.hasNext();) { 1259 CreditMemoItem cmItem = (CreditMemoItem) items.next(); 1260 PurchaseOrderItem poItem = getPoItem(po, cmItem.getItemLineNumber(), cmItem.getItemType()); 1261 1262 KualiDecimal itemDisEncumber = null; // Amount to disencumber for this item 1263 KualiDecimal itemAlterInvoiceAmt = null; // Amount to alter the invoicedAmt on the PO item 1264 1265 String logItmNbr = "Item # " + cmItem.getItemLineNumber(); 1266 LOG.debug("getCreditMemoEncumbrance() " + logItmNbr); 1267 1268 final KualiDecimal cmItemTotalAmount = (cmItem.getTotalAmount() == null) ? KualiDecimal.ZERO : cmItem.getTotalAmount(); 1269 ; 1270 // If there isn't a PO item or the total amount is 0, we don't need encumbrances 1271 if ((poItem == null) || (cmItemTotalAmount == null) || (cmItemTotalAmount.doubleValue() == 0)) { 1272 LOG.debug("getCreditMemoEncumbrance() " + logItmNbr + " No encumbrances required"); 1273 } 1274 else { 1275 LOG.debug("getCreditMemoEncumbrance() " + logItmNbr + " Calculate encumbrance GL entries"); 1276 1277 // Do we calculate the encumbrance amount based on quantity or amount? 1278 if (poItem.getItemType().isQuantityBasedGeneralLedgerIndicator()) { 1279 LOG.debug("getCreditMemoEncumbrance() " + logItmNbr + " Calculate encumbrance based on quantity"); 1280 1281 // Do encumbrance calculations based on quantity 1282 KualiDecimal cmQuantity = cmItem.getItemQuantity() == null ? ZERO : cmItem.getItemQuantity(); 1283 1284 KualiDecimal encumbranceQuantityChange = calculateQuantityChange(cancel, poItem, cmQuantity); 1285 1286 LOG.debug("getCreditMemoEncumbrance() " + logItmNbr + " encumbranceQtyChange " + encumbranceQuantityChange + " outstandingEncumberedQty " + poItem.getItemOutstandingEncumberedQuantity() + " invoicedTotalQuantity " + poItem.getItemInvoicedTotalQuantity()); 1287 1288 //do math as big decimal as doing it as a KualiDecimal will cause the item price to round to 2 digits 1289 itemDisEncumber = new KualiDecimal(encumbranceQuantityChange.bigDecimalValue().multiply(poItem.getItemUnitPrice())); 1290 1291 //add tax for encumbrance 1292 KualiDecimal itemTaxAmount = poItem.getItemTaxAmount() == null ? ZERO : poItem.getItemTaxAmount(); 1293 KualiDecimal encumbranceTaxAmount = encumbranceQuantityChange.divide(poItem.getItemQuantity()).multiply(itemTaxAmount); 1294 itemDisEncumber = itemDisEncumber.add(encumbranceTaxAmount); 1295 1296 itemAlterInvoiceAmt = cmItemTotalAmount; 1297 if (cancel) { 1298 itemAlterInvoiceAmt = itemAlterInvoiceAmt.multiply(new KualiDecimal("-1")); 1299 } 1300 } 1301 else { 1302 LOG.debug("getCreditMemoEncumbrance() " + logItmNbr + " Calculate encumbrance based on amount"); 1303 1304 // Do encumbrance calculations based on amount only 1305 if (cancel) { 1306 // Decrease encumbrance 1307 itemDisEncumber = cmItemTotalAmount.multiply(new KualiDecimal("-1")); 1308 1309 if (poItem.getItemOutstandingEncumberedAmount().add(itemDisEncumber).doubleValue() < 0) { 1310 LOG.debug("getCreditMemoEncumbrance() Cancel overflow"); 1311 1312 itemDisEncumber = poItem.getItemOutstandingEncumberedAmount(); 1313 } 1314 } 1315 else { 1316 // Increase encumbrance 1317 itemDisEncumber = cmItemTotalAmount; 1318 1319 if (poItem.getItemOutstandingEncumberedAmount().add(itemDisEncumber).doubleValue() > poItem.getTotalAmount().doubleValue()) { 1320 LOG.debug("getCreditMemoEncumbrance() Create overflow"); 1321 1322 itemDisEncumber = poItem.getTotalAmount().subtract(poItem.getItemOutstandingEncumberedAmount()); 1323 } 1324 } 1325 itemAlterInvoiceAmt = itemDisEncumber; 1326 } 1327 1328 // alter the encumbrance based on what was originally encumbered 1329 poItem.setItemOutstandingEncumberedAmount(poItem.getItemOutstandingEncumberedAmount().add(itemDisEncumber)); 1330 1331 // alter the invoiced amt based on what was actually credited on the credit memo 1332 poItem.setItemInvoicedTotalAmount(poItem.getItemInvoicedTotalAmount().subtract(itemAlterInvoiceAmt)); 1333 if (poItem.getItemInvoicedTotalAmount().compareTo(ZERO) < 0) { 1334 poItem.setItemInvoicedTotalAmount(ZERO); 1335 } 1336 1337 1338 LOG.debug("getCreditMemoEncumbrance() " + logItmNbr + " Amount to disencumber: " + itemDisEncumber); 1339 1340 // Sort accounts 1341 Collections.sort((List) poItem.getSourceAccountingLines()); 1342 1343 // make the list of accounts for the disencumbrance entry 1344 PurchaseOrderAccount lastAccount = null; 1345 KualiDecimal accountTotal = ZERO; 1346 // Collections.sort((List)poItem.getSourceAccountingLines()); 1347 for (Iterator accountIter = poItem.getSourceAccountingLines().iterator(); accountIter.hasNext();) { 1348 PurchaseOrderAccount account = (PurchaseOrderAccount) accountIter.next(); 1349 if (!account.isEmpty()) { 1350 KualiDecimal encumbranceAmount = null; 1351 1352 SourceAccountingLine acctString = account.generateSourceAccountingLine(); 1353 // amount = item disencumber * account percent / 100 1354 encumbranceAmount = itemDisEncumber.multiply(new KualiDecimal(account.getAccountLinePercent().toString())).divide(new KualiDecimal(100)); 1355 1356 account.setItemAccountOutstandingEncumbranceAmount(account.getItemAccountOutstandingEncumbranceAmount().add(encumbranceAmount)); 1357 1358 // For rounding check at the end 1359 accountTotal = accountTotal.add(encumbranceAmount); 1360 1361 lastAccount = account; 1362 1363 LOG.debug("getCreditMemoEncumbrance() " + logItmNbr + " " + acctString + " = " + encumbranceAmount); 1364 1365 if (encumbranceAccountMap.get(acctString) == null) { 1366 encumbranceAccountMap.put(acctString, encumbranceAmount); 1367 } 1368 else { 1369 KualiDecimal amt = (KualiDecimal) encumbranceAccountMap.get(acctString); 1370 encumbranceAccountMap.put(acctString, amt.add(encumbranceAmount)); 1371 } 1372 } 1373 } 1374 1375 // account for rounding by adjusting last account as needed 1376 if (lastAccount != null) { 1377 KualiDecimal difference = itemDisEncumber.subtract(accountTotal); 1378 LOG.debug("getCreditMemoEncumbrance() difference: " + logItmNbr + " " + difference); 1379 1380 SourceAccountingLine acctString = lastAccount.generateSourceAccountingLine(); 1381 KualiDecimal amount = (KualiDecimal) encumbranceAccountMap.get(acctString); 1382 if (amount == null) { 1383 encumbranceAccountMap.put(acctString, difference); 1384 } 1385 else { 1386 encumbranceAccountMap.put(acctString, amount.add(difference)); 1387 } 1388 lastAccount.setItemAccountOutstandingEncumbranceAmount(lastAccount.getItemAccountOutstandingEncumbranceAmount().add(difference)); 1389 } 1390 } 1391 } 1392 1393 List<SourceAccountingLine> encumbranceAccounts = new ArrayList(); 1394 for (Iterator iter = encumbranceAccountMap.keySet().iterator(); iter.hasNext();) { 1395 SourceAccountingLine acctString = (SourceAccountingLine) iter.next(); 1396 KualiDecimal amount = (KualiDecimal) encumbranceAccountMap.get(acctString); 1397 if (amount.doubleValue() != 0) { 1398 acctString.setAmount(amount); 1399 encumbranceAccounts.add(acctString); 1400 } 1401 } 1402 1403 SpringContext.getBean(PurapService.class).saveDocumentNoValidation(po); 1404 1405 return encumbranceAccounts; 1406 } 1407 1408 /** 1409 * Save the given general ledger entries 1410 * 1411 * @param glEntries List of GeneralLedgerPendingEntries to be saved 1412 */ 1413 protected void saveGLEntries(List<GeneralLedgerPendingEntry> glEntries) { 1414 LOG.debug("saveGLEntries() started"); 1415 businessObjectService.save(glEntries); 1416 } 1417 1418 /** 1419 * Save the given accounts for the given document. 1420 * 1421 * @param sourceLines Accounts to be saved 1422 * @param purapDocumentIdentifier Purap document id for accounts 1423 */ 1424 protected void saveAccountsPayableSummaryAccounts(List<SummaryAccount> summaryAccounts, Integer purapDocumentIdentifier, String docType) { 1425 LOG.debug("saveAccountsPayableSummaryAccounts() started"); 1426 purapAccountingService.deleteSummaryAccounts(purapDocumentIdentifier, docType); 1427 List<AccountsPayableSummaryAccount> apSummaryAccounts = new ArrayList(); 1428 for (SummaryAccount summaryAccount : summaryAccounts) { 1429 apSummaryAccounts.add(new AccountsPayableSummaryAccount(summaryAccount.getAccount(), purapDocumentIdentifier, docType)); 1430 } 1431 businessObjectService.save(apSummaryAccounts); 1432 } 1433 1434 /** 1435 * Find item in PO based on given parameters. Must send either the line # or item type. 1436 * 1437 * @param po Purchase Order containing list of items 1438 * @param nbr Line # of desired item (could be null) 1439 * @param itemType Item type of desired item 1440 * @return PurcahseOrderItem found matching given criteria 1441 */ 1442 protected PurchaseOrderItem getPoItem(PurchaseOrderDocument po, Integer nbr, ItemType itemType) { 1443 LOG.debug("getPoItem() started"); 1444 for (Iterator iter = po.getItems().iterator(); iter.hasNext();) { 1445 PurchaseOrderItem element = (PurchaseOrderItem) iter.next(); 1446 if (itemType.isLineItemIndicator()) { 1447 if (ObjectUtils.isNotNull(nbr) && ObjectUtils.isNotNull(element.getItemLineNumber()) && (nbr.compareTo(element.getItemLineNumber()) == 0)) { 1448 return element; 1449 } 1450 } 1451 else { 1452 if (element.getItemTypeCode().equals(itemType.getItemTypeCode())) { 1453 return element; 1454 } 1455 } 1456 } 1457 return null; 1458 } 1459 1460 /** 1461 * Format description for general ledger entry. Currently making sure length is less than 40 char. 1462 * 1463 * @param description String to be formatted 1464 * @return Formatted String 1465 */ 1466 protected String entryDescription(String description) { 1467 if (description != null && description.length() > 40) { 1468 return description.toString().substring(0, 39); 1469 } 1470 else { 1471 return description; 1472 } 1473 } 1474 1475 /** 1476 * Calculate quantity change for creating Credit Memo entries 1477 * 1478 * @param cancel Boolean indicating whether entries are for creation or cancellation of credit memo 1479 * @param poItem Purchase Order Item 1480 * @param cmQuantity Quantity on credit memo item 1481 * @return Calculated change 1482 */ 1483 protected KualiDecimal calculateQuantityChange(boolean cancel, PurchaseOrderItem poItem, KualiDecimal cmQuantity) { 1484 LOG.debug("calculateQuantityChange() started"); 1485 1486 // Calculate quantity change & adjust invoiced quantity & outstanding encumbered quantity 1487 KualiDecimal encumbranceQuantityChange = null; 1488 if (cancel) { 1489 encumbranceQuantityChange = cmQuantity.multiply(new KualiDecimal("-1")); 1490 } 1491 else { 1492 encumbranceQuantityChange = cmQuantity; 1493 } 1494 poItem.setItemInvoicedTotalQuantity(poItem.getItemInvoicedTotalQuantity().subtract(encumbranceQuantityChange)); 1495 poItem.setItemOutstandingEncumberedQuantity(poItem.getItemOutstandingEncumberedQuantity().add(encumbranceQuantityChange)); 1496 1497 // Check for overflows 1498 if (cancel) { 1499 if (poItem.getItemOutstandingEncumberedQuantity().doubleValue() < 0) { 1500 LOG.debug("calculateQuantityChange() Cancel overflow"); 1501 KualiDecimal difference = poItem.getItemOutstandingEncumberedQuantity().abs(); 1502 poItem.setItemOutstandingEncumberedQuantity(ZERO); 1503 poItem.setItemInvoicedTotalQuantity(poItem.getItemQuantity()); 1504 encumbranceQuantityChange = encumbranceQuantityChange.add(difference); 1505 } 1506 } 1507 else { 1508 if (poItem.getItemInvoicedTotalQuantity().doubleValue() < 0) { 1509 LOG.debug("calculateQuantityChange() Create overflow"); 1510 KualiDecimal difference = poItem.getItemInvoicedTotalQuantity().abs(); 1511 poItem.setItemOutstandingEncumberedQuantity(poItem.getItemQuantity()); 1512 poItem.setItemInvoicedTotalQuantity(ZERO); 1513 encumbranceQuantityChange = encumbranceQuantityChange.add(difference); 1514 } 1515 } 1516 return encumbranceQuantityChange; 1517 } 1518 1519 public void setDateTimeService(DateTimeService dateTimeService) { 1520 this.dateTimeService = dateTimeService; 1521 } 1522 1523 public void setBusinessObjectService(BusinessObjectService businessObjectService) { 1524 this.businessObjectService = businessObjectService; 1525 } 1526 1527 public void setGeneralLedgerPendingEntryService(GeneralLedgerPendingEntryService generalLedgerPendingEntryService) { 1528 this.generalLedgerPendingEntryService = generalLedgerPendingEntryService; 1529 } 1530 1531 public void setKualiRuleService(KualiRuleService kualiRuleService) { 1532 this.kualiRuleService = kualiRuleService; 1533 } 1534 1535 public void setPurapAccountingService(PurapAccountingService purapAccountingService) { 1536 this.purapAccountingService = purapAccountingService; 1537 } 1538 1539 public void setUniversityDateService(UniversityDateService universityDateService) { 1540 this.universityDateService = universityDateService; 1541 } 1542 1543 public void setPurchaseOrderService(PurchaseOrderService purchaseOrderService) { 1544 this.purchaseOrderService = purchaseOrderService; 1545 } 1546 1547 public void setObjectCodeService(ObjectCodeService objectCodeService) { 1548 this.objectCodeService = objectCodeService; 1549 } 1550 1551 public void setSubObjectCodeService(SubObjectCodeService subObjectCodeService) { 1552 this.subObjectCodeService = subObjectCodeService; 1553 } 1554 1555 public void setParameterService(ParameterService parameterService) { 1556 this.parameterService = parameterService; 1557 } 1558 1559 public void setPaymentRequestService(PaymentRequestService paymentRequestService) { 1560 this.paymentRequestService = paymentRequestService; 1561 } 1562 1563 }