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 }