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    }