001    /*
002     * Copyright 2011 The Kuali Foundation.
003     * 
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     * 
008     * http://www.opensource.org/licenses/ecl2.php
009     * 
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.kfs.module.purap.document.service.impl;
017    
018    import java.math.BigDecimal;
019    import java.sql.Date;
020    import java.sql.Timestamp;
021    import java.util.ArrayList;
022    import java.util.Collection;
023    import java.util.HashMap;
024    import java.util.HashSet;
025    import java.util.Iterator;
026    import java.util.List;
027    import java.util.Set;
028    
029    import org.apache.commons.collections.CollectionUtils;
030    import org.apache.commons.lang.StringUtils;
031    import org.kuali.kfs.module.purap.PurapConstants;
032    import org.kuali.kfs.module.purap.PurapKeyConstants;
033    import org.kuali.kfs.module.purap.PurapParameterConstants;
034    import org.kuali.kfs.module.purap.PurapConstants.CreditMemoStatuses;
035    import org.kuali.kfs.module.purap.PurapWorkflowConstants.NodeDetails;
036    import org.kuali.kfs.module.purap.PurapWorkflowConstants.CreditMemoDocument.NodeDetailEnum;
037    import org.kuali.kfs.module.purap.businessobject.CreditMemoAccount;
038    import org.kuali.kfs.module.purap.businessobject.CreditMemoItem;
039    import org.kuali.kfs.module.purap.businessobject.PaymentRequestItem;
040    import org.kuali.kfs.module.purap.businessobject.PurApAccountingLine;
041    import org.kuali.kfs.module.purap.businessobject.PurchaseOrderItem;
042    import org.kuali.kfs.module.purap.businessobject.PurchasingCapitalAssetItem;
043    import org.kuali.kfs.module.purap.document.AccountsPayableDocument;
044    import org.kuali.kfs.module.purap.document.PaymentRequestDocument;
045    import org.kuali.kfs.module.purap.document.PurchaseOrderDocument;
046    import org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument;
047    import org.kuali.kfs.module.purap.document.VendorCreditMemoDocument;
048    import org.kuali.kfs.module.purap.document.dataaccess.CreditMemoDao;
049    import org.kuali.kfs.module.purap.document.service.AccountsPayableService;
050    import org.kuali.kfs.module.purap.document.service.CreditMemoService;
051    import org.kuali.kfs.module.purap.document.service.PaymentRequestService;
052    import org.kuali.kfs.module.purap.document.service.PurapService;
053    import org.kuali.kfs.module.purap.document.service.PurchaseOrderService;
054    import org.kuali.kfs.module.purap.document.validation.event.AttributedContinuePurapEvent;
055    import org.kuali.kfs.module.purap.service.PurapAccountingService;
056    import org.kuali.kfs.module.purap.service.PurapGeneralLedgerService;
057    import org.kuali.kfs.module.purap.util.ExpiredOrClosedAccountEntry;
058    import org.kuali.kfs.module.purap.util.VendorGroupingHelper;
059    import org.kuali.kfs.sys.businessobject.Bank;
060    import org.kuali.kfs.sys.businessobject.SourceAccountingLine;
061    import org.kuali.kfs.sys.context.SpringContext;
062    import org.kuali.kfs.sys.service.BankService;
063    import org.kuali.kfs.vnd.VendorConstants;
064    import org.kuali.kfs.vnd.VendorUtils;
065    import org.kuali.kfs.vnd.businessobject.VendorAddress;
066    import org.kuali.kfs.vnd.businessobject.VendorDetail;
067    import org.kuali.kfs.vnd.document.service.VendorService;
068    import org.kuali.rice.kew.exception.WorkflowException;
069    import org.kuali.rice.kim.bo.Person;
070    import org.kuali.rice.kns.bo.DocumentHeader;
071    import org.kuali.rice.kns.bo.Note;
072    import org.kuali.rice.kns.exception.ValidationException;
073    import org.kuali.rice.kns.service.DataDictionaryService;
074    import org.kuali.rice.kns.service.DocumentService;
075    import org.kuali.rice.kns.service.KualiConfigurationService;
076    import org.kuali.rice.kns.service.NoteService;
077    import org.kuali.rice.kns.util.GlobalVariables;
078    import org.kuali.rice.kns.util.KNSPropertyConstants;
079    import org.kuali.rice.kns.util.KualiDecimal;
080    import org.kuali.rice.kns.util.ObjectUtils;
081    import org.kuali.rice.kns.workflow.service.KualiWorkflowDocument;
082    import org.kuali.rice.kns.workflow.service.WorkflowDocumentService;
083    import org.springframework.transaction.annotation.Transactional;
084    
085    /**
086     * Provides services to support the creation of a Credit Memo Document.
087     */
088    @Transactional
089    public class CreditMemoServiceImpl implements CreditMemoService {
090        private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(CreditMemoServiceImpl.class);
091    
092        private AccountsPayableService accountsPayableService;
093        private CreditMemoDao creditMemoDao;
094        private DataDictionaryService dataDictionaryService;
095        private DocumentService documentService;
096        private KualiConfigurationService kualiConfigurationService;
097        private NoteService noteService;
098        private PaymentRequestService paymentRequestService;
099        private PurapAccountingService purapAccountingService;
100        private PurapGeneralLedgerService purapGeneralLedgerService;
101        private PurapService purapService;
102        private PurchaseOrderService purchaseOrderService;
103        private VendorService vendorService;
104        private WorkflowDocumentService workflowDocumentService;
105        
106    
107        public void setAccountsPayableService(AccountsPayableService accountsPayableService) {
108            this.accountsPayableService = accountsPayableService;
109        }
110    
111        public void setCreditMemoDao(CreditMemoDao creditMemoDao) {
112            this.creditMemoDao = creditMemoDao;
113        }
114    
115        public void setDataDictionaryService(DataDictionaryService dataDictionaryService) {
116            this.dataDictionaryService = dataDictionaryService;
117        }
118    
119        public void setDocumentService(DocumentService documentService) {
120            this.documentService = documentService;
121        }
122    
123        public void setKualiConfigurationService(KualiConfigurationService kualiConfigurationService) {
124            this.kualiConfigurationService = kualiConfigurationService;
125        }
126    
127        public void setNoteService(NoteService noteService) {
128            this.noteService = noteService;
129        }
130    
131        public void setPaymentRequestService(PaymentRequestService paymentRequestService) {
132            this.paymentRequestService = paymentRequestService;
133        }
134    
135        public void setPurapAccountingService(PurapAccountingService purapAccountingService) {
136            this.purapAccountingService = purapAccountingService;
137        }
138    
139        public void setPurapGeneralLedgerService(PurapGeneralLedgerService purapGeneralLedgerService) {
140            this.purapGeneralLedgerService = purapGeneralLedgerService;
141        }
142    
143        public void setPurapService(PurapService purapService) {
144            this.purapService = purapService;
145        }
146    
147        public void setPurchaseOrderService(PurchaseOrderService purchaseOrderService) {
148            this.purchaseOrderService = purchaseOrderService;
149        }
150    
151        public void setVendorService(VendorService vendorService) {
152            this.vendorService = vendorService;
153        }
154    
155        public void setWorkflowDocumentService(WorkflowDocumentService workflowDocumentService){
156            this.workflowDocumentService = workflowDocumentService;
157        }
158        
159    
160        
161        /**
162         * @see org.kuali.kfs.module.purap.document.service.CreditMemoService#getCreditMemosToExtract(java.lang.String)
163         */
164        public Iterator<VendorCreditMemoDocument> getCreditMemosToExtract(String chartCode) {
165            LOG.debug("getCreditMemosToExtract() started");
166    
167            return creditMemoDao.getCreditMemosToExtract(chartCode);
168        }
169    
170        public Collection<VendorCreditMemoDocument> getCreditMemosToExtractByVendor(String chartCode, VendorGroupingHelper vendor ) {
171            LOG.debug("getCreditMemosToExtractByVendor() started");
172    
173            return creditMemoDao.getCreditMemosToExtractByVendor(chartCode,vendor);
174        }
175    
176        public Set<VendorGroupingHelper> getVendorsOnCreditMemosToExtract(String chartCode) {
177            LOG.debug("getVendorsOnCreditMemosToExtract() started");
178            HashSet<VendorGroupingHelper> vendors = new HashSet<VendorGroupingHelper>();
179            
180            Iterator<VendorCreditMemoDocument> docs = getCreditMemosToExtract(chartCode);
181            while ( docs.hasNext() ) {
182                VendorCreditMemoDocument doc = docs.next();
183                vendors.add( new VendorGroupingHelper( doc ) );
184            }
185            return vendors;
186        }
187    
188        /**
189         * @see org.kuali.kfs.module.purap.document.service.CreditMemoService#creditMemoDuplicateMessages(org.kuali.kfs.module.purap.document.CreditMemoDocument)
190         */
191        public String creditMemoDuplicateMessages(VendorCreditMemoDocument cmDocument) {
192            String duplicateMessage = null;
193    
194            String vendorNumber = cmDocument.getVendorNumber();
195            if (StringUtils.isEmpty(vendorNumber)) {
196                PurchasingAccountsPayableDocument sourceDocument = cmDocument.getPurApSourceDocumentIfPossible();
197                if (ObjectUtils.isNotNull(sourceDocument)) {
198                    vendorNumber = sourceDocument.getVendorNumber();
199                }
200            }
201    
202            if (StringUtils.isNotEmpty(vendorNumber)) {
203                // check for existence of another credit memo with the same vendor and vendor credit memo number
204                if (creditMemoDao.duplicateExists(VendorUtils.getVendorHeaderId(vendorNumber), VendorUtils.getVendorDetailId(vendorNumber), cmDocument.getCreditMemoNumber())) {
205                    duplicateMessage = kualiConfigurationService.getPropertyString(PurapKeyConstants.MESSAGE_DUPLICATE_CREDIT_MEMO_VENDOR_NUMBER);
206                }
207    
208                // check for existence of another credit memo with the same vendor and credit memo date
209                if (creditMemoDao.duplicateExists(VendorUtils.getVendorHeaderId(vendorNumber), VendorUtils.getVendorDetailId(vendorNumber), cmDocument.getCreditMemoDate(), cmDocument.getCreditMemoAmount())) {
210                    duplicateMessage = kualiConfigurationService.getPropertyString(PurapKeyConstants.MESSAGE_DUPLICATE_CREDIT_MEMO_VENDOR_NUMBER_DATE_AMOUNT);
211                }
212            }
213    
214            return duplicateMessage;
215        }
216    
217        /**
218         * @see org.kuali.kfs.module.purap.document.service.CreditMemoService#getPOInvoicedItems(org.kuali.kfs.module.purap.document.PurchaseOrderDocument)
219         */
220        public List<PurchaseOrderItem> getPOInvoicedItems(PurchaseOrderDocument poDocument) {
221            List<PurchaseOrderItem> invoicedItems = new ArrayList<PurchaseOrderItem>();
222    
223            for (Iterator iter = poDocument.getItems().iterator(); iter.hasNext();) {
224                PurchaseOrderItem poItem = (PurchaseOrderItem) iter.next();
225    
226                // only items of type above the line can be considered for being invoiced
227                if (poItem.getItemType().isAdditionalChargeIndicator()) {
228                    continue;
229                }
230    
231                if (poItem.getItemType().isQuantityBasedGeneralLedgerIndicator() && poItem.getItemInvoicedTotalQuantity().isGreaterThan(KualiDecimal.ZERO)) {
232                    invoicedItems.add(poItem);
233                }
234                else {
235                    BigDecimal unitPrice = (poItem.getItemUnitPrice() == null ? new BigDecimal(0) : poItem.getItemUnitPrice());
236                    if (unitPrice.doubleValue() > poItem.getItemOutstandingEncumberedAmount().doubleValue()) {
237                        invoicedItems.add(poItem);
238                    }
239                }
240            }
241    
242            return invoicedItems;
243        }
244    
245    
246        /**
247         * @see org.kuali.kfs.module.purap.document.service.CreditMemoService#calculateCreditMemo(org.kuali.kfs.module.purap.document.CreditMemoDocument)
248         */
249        public void calculateCreditMemo(VendorCreditMemoDocument cmDocument) {
250    
251            cmDocument.updateExtendedPriceOnItems();
252    
253            for (CreditMemoItem item : (List<CreditMemoItem>) cmDocument.getItems()) {
254                // make sure restocking fee is negative
255                if (StringUtils.equals(PurapConstants.ItemTypeCodes.ITEM_TYPE_RESTCK_FEE_CODE, item.getItemTypeCode())) {
256                    if (item.getItemUnitPrice() != null) {
257                        item.setExtendedPrice(item.getExtendedPrice().abs().negated());
258                        item.setItemUnitPrice(item.getItemUnitPrice().abs().negate());
259                    }
260                }
261            }
262    
263            //calculate tax if cm not based on vendor
264            if (cmDocument.isSourceVendor() == false) {
265                purapService.calculateTax(cmDocument);
266            }
267            
268            // proration
269            if (cmDocument.isSourceVendor()) {
270                // no proration on vendor
271                return;
272            }
273    
274            for (CreditMemoItem item : (List<CreditMemoItem>) cmDocument.getItems()) {
275    
276                // skip above the line
277                if (item.getItemType().isLineItemIndicator()) {
278                    continue;
279                }
280    
281                if ((item.getSourceAccountingLines().isEmpty()) && (ObjectUtils.isNotNull(item.getExtendedPrice())) && (KualiDecimal.ZERO.compareTo(item.getExtendedPrice()) != 0)) {
282    
283                    KualiDecimal totalAmount = KualiDecimal.ZERO;
284                    List<PurApAccountingLine> distributedAccounts = null;
285                    List<SourceAccountingLine> summaryAccounts = null;
286    
287                    totalAmount = cmDocument.getPurApSourceDocumentIfPossible().getTotalDollarAmount();
288                    // this should do nothing on preq which is fine
289                    purapAccountingService.updateAccountAmounts(cmDocument.getPurApSourceDocumentIfPossible());
290                    summaryAccounts = purapAccountingService.generateSummary(cmDocument.getPurApSourceDocumentIfPossible().getItems());
291                    distributedAccounts = purapAccountingService.generateAccountDistributionForProration(summaryAccounts, totalAmount, PurapConstants.PRORATION_SCALE, CreditMemoAccount.class);
292    
293                    if (CollectionUtils.isNotEmpty(distributedAccounts) && CollectionUtils.isEmpty(item.getSourceAccountingLines())) {
294                        item.setSourceAccountingLines(distributedAccounts);
295                    }
296                }
297            }
298            // end proration
299        }
300    
301        /**
302         * @see org.kuali.kfs.module.purap.document.service.CreditMemoService#getCreditMemoByDocumentNumber(java.lang.String)
303         */
304        public VendorCreditMemoDocument getCreditMemoByDocumentNumber(String documentNumber) {
305            LOG.debug("getCreditMemoByDocumentNumber() started");
306    
307            if (ObjectUtils.isNotNull(documentNumber)) {
308                try {
309                    VendorCreditMemoDocument doc = (VendorCreditMemoDocument) documentService.getByDocumentHeaderId(documentNumber);
310                    return doc;
311                }
312                catch (WorkflowException e) {
313                    String errorMessage = "Error getting credit memo document from document service";
314                    LOG.error("getCreditMemoByDocumentNumber() " + errorMessage, e);
315                    throw new RuntimeException(errorMessage, e);
316                }
317            }
318            return null;
319        }
320    
321        /**
322         * @see org.kuali.kfs.module.purap.document.service.CreditMemoService#getCreditMemoDocumentById(java.lang.Integer)
323         */
324        public VendorCreditMemoDocument getCreditMemoDocumentById(Integer purchasingDocumentIdentifier) {
325            return getCreditMemoByDocumentNumber(creditMemoDao.getDocumentNumberByCreditMemoId(purchasingDocumentIdentifier));
326        }
327    
328        /**
329         * @see org.kuali.kfs.module.purap.document.service.CreditMemoService#saveDocument(org.kuali.kfs.module.purap.document.CreditMemoDocument)
330         */
331        public void populateAndSaveCreditMemo(VendorCreditMemoDocument document) {
332            try {
333                document.setStatusCode(PurapConstants.CreditMemoStatuses.IN_PROCESS);
334                
335                if (document.isSourceDocumentPaymentRequest()) {
336                    document.setBankCode(document.getPaymentRequestDocument().getBankCode());
337                    document.setBank(document.getPaymentRequestDocument().getBank());
338                }
339                else {
340                    // set bank code to default bank code in the system parameter
341                    Bank defaultBank = SpringContext.getBean(BankService.class).getDefaultBankByDocType(document.getClass());
342                    if (defaultBank != null) {
343                        document.setBankCode(defaultBank.getBankCode());
344                        document.setBank(defaultBank);
345                    }
346                }
347                
348                documentService.saveDocument(document, AttributedContinuePurapEvent.class);
349            }
350            catch (ValidationException ve) {
351                document.setStatusCode(PurapConstants.CreditMemoStatuses.INITIATE);
352            }
353            catch (WorkflowException we) {
354                // set the status back to initiate
355                document.setStatusCode(PurapConstants.CreditMemoStatuses.INITIATE);
356                String errorMsg = "Error saving document # " + document.getDocumentHeader().getDocumentNumber() + " " + we.getMessage();
357                LOG.error(errorMsg, we);
358                throw new RuntimeException(errorMsg, we);
359            }
360        }
361    
362        /**
363         * @see org.kuali.kfs.module.purap.document.service.CreditMemoService#reopenClosedPO(org.kuali.kfs.module.purap.document.CreditMemoDocument)
364         */
365        public void reopenClosedPO(VendorCreditMemoDocument cmDocument) {
366            // reopen PO if closed
367            Integer purchaseOrderDocumentId = cmDocument.getPurchaseOrderIdentifier();
368            if (cmDocument.isSourceDocumentPaymentRequest() && ObjectUtils.isNull(purchaseOrderDocumentId)) {
369                PaymentRequestDocument paymentRequestDocument = paymentRequestService.getPaymentRequestById(cmDocument.getPaymentRequestIdentifier());
370                purchaseOrderDocumentId = paymentRequestDocument.getPurchaseOrderIdentifier();
371            }
372            // if we found a valid po id number then check it for reopening
373            if (ObjectUtils.isNotNull(purchaseOrderDocumentId)) {
374                PurchaseOrderDocument purchaseOrderDocument = purchaseOrderService.getCurrentPurchaseOrder(purchaseOrderDocumentId);
375                // only reopen if the po is not null, it does not have a pending change already scheduled, and it is in closed status
376                if (ObjectUtils.isNotNull(purchaseOrderDocument) && (!purchaseOrderDocument.isPendingActionIndicator()) && PurapConstants.PurchaseOrderStatuses.CLOSED.equals(purchaseOrderDocument.getStatusCode())) {
377    
378                }
379            }
380        }
381    
382        /**
383         * @see org.kuali.kfs.module.purap.document.service.CreditMemoService#addHoldOnPaymentRequest(org.kuali.kfs.module.purap.document.CreditMemoDocument,
384         *      java.lang.String)
385         */
386        public VendorCreditMemoDocument addHoldOnCreditMemo(VendorCreditMemoDocument cmDocument, String note) throws Exception {
387            // save the note
388            Note noteObj = documentService.createNoteFromDocument(cmDocument, note);
389            documentService.addNoteToDocument(cmDocument, noteObj);
390            noteService.save(noteObj);
391    
392            // retrieve and save with hold indicator set to true
393            VendorCreditMemoDocument cmDoc = getCreditMemoDocumentById(cmDocument.getPurapDocumentIdentifier());
394            cmDoc.setHoldIndicator(true);
395            cmDoc.setLastActionPerformedByPersonId(GlobalVariables.getUserSession().getPerson().getPrincipalId());
396            purapService.saveDocumentNoValidation(cmDoc);
397    
398            // must also save it on the incoming document
399            cmDocument.setHoldIndicator(true);
400            cmDocument.setLastActionPerformedByPersonId(GlobalVariables.getUserSession().getPerson().getPrincipalId());
401            
402            return cmDoc;
403        }
404    
405        /**
406         * @see org.kuali.kfs.module.purap.document.service.CreditMemoService#removeHoldOnCreditMemo(org.kuali.kfs.module.purap.document.CreditMemoDocument,
407         *      java.lang.String)
408         */
409        public VendorCreditMemoDocument removeHoldOnCreditMemo(VendorCreditMemoDocument cmDocument, String note) throws Exception {
410            // save the note
411            Note noteObj = documentService.createNoteFromDocument(cmDocument, note);
412            documentService.addNoteToDocument(cmDocument, noteObj);
413            noteService.save(noteObj);
414    
415            // retrieve and save with hold indicator set to false
416            VendorCreditMemoDocument cmDoc = getCreditMemoDocumentById(cmDocument.getPurapDocumentIdentifier());
417            cmDoc.setHoldIndicator(false);
418            cmDoc.setLastActionPerformedByPersonId(null);
419            purapService.saveDocumentNoValidation(cmDoc);
420    
421            // must also save it on the incoming document
422            cmDocument.setHoldIndicator(false);
423            cmDocument.setLastActionPerformedByPersonId(null);
424            
425            return cmDoc;
426        }
427    
428        /**
429         * @see org.kuali.kfs.module.purap.document.service.AccountsPayableDocumentSpecificService#updateStatusByNode(java.lang.String, org.kuali.kfs.module.purap.document.AccountsPayableDocument)
430         */
431        public String updateStatusByNode(String currentNodeName, AccountsPayableDocument apDoc) {
432            return updateStatusByNode(currentNodeName, (VendorCreditMemoDocument) apDoc);
433        }
434    
435        /**
436         * Updates the status of a credit memo document, currently this is used by the cancel action
437         * 
438         * @param currentNodeName  The string representing the current node to be used to obtain the canceled status code.
439         * @param cmDoc            The credit memo document to be updated.
440         * @return                 The string representing the canceledStatusCode, if empty it is assumed to be not from workflow. 
441         */
442        protected String updateStatusByNode(String currentNodeName, VendorCreditMemoDocument cmDoc) {
443            // update the status on the document
444    
445            String cancelledStatusCode = "";
446            if (StringUtils.isEmpty(currentNodeName)) {
447                cancelledStatusCode = PurapConstants.CreditMemoStatuses.CANCELLED_POST_AP_APPROVE;
448            }
449            else {
450                NodeDetails currentNode = NodeDetailEnum.getNodeDetailEnumByName(currentNodeName);
451                if (ObjectUtils.isNotNull(currentNode)) {
452                    cancelledStatusCode = currentNode.getDisapprovedStatusCode();
453                }
454            }
455    
456            if (StringUtils.isNotBlank(cancelledStatusCode)) {
457                purapService.updateStatus(cmDoc, cancelledStatusCode);
458                purapService.saveDocumentNoValidation(cmDoc);
459                return cancelledStatusCode;
460            }
461            else {
462                logAndThrowRuntimeException("No status found to set for document being disapproved in node '" + currentNodeName + "'");
463            }
464            return cancelledStatusCode;
465        }
466    
467        /**
468         * @see org.kuali.kfs.module.purap.document.service.CreditMemoService#cancelExtractedCreditMemo(org.kuali.kfs.module.purap.document.CreditMemoDocument,
469         *      java.lang.String)
470         */
471        public void cancelExtractedCreditMemo(VendorCreditMemoDocument cmDocument, String note) {
472            LOG.debug("cancelExtractedCreditMemo() started");
473            if (CreditMemoStatuses.CANCELLED_STATUSES.contains(cmDocument.getStatusCode())) {
474                LOG.debug("cancelExtractedCreditMemo() ended");
475                return;
476            }
477    
478            try {
479                Note noteObj = documentService.createNoteFromDocument(cmDocument, note);
480                documentService.addNoteToDocument(cmDocument, noteObj);
481            }
482            catch (Exception e) {
483                throw new RuntimeException(e.getMessage());
484            }
485    
486            accountsPayableService.cancelAccountsPayableDocument(cmDocument, "");
487            LOG.debug("cancelExtractedCreditMemo() CM " + cmDocument.getPurapDocumentIdentifier() + " Cancelled Without Workflow");
488            LOG.debug("cancelExtractedCreditMemo() ended");
489    
490        }
491    
492        /**
493         * @see org.kuali.kfs.module.purap.document.service.CreditMemoService#resetExtractedCreditMemo(org.kuali.kfs.module.purap.document.CreditMemoDocument,
494         *      java.lang.String)
495         */
496        public void resetExtractedCreditMemo(VendorCreditMemoDocument cmDocument, String note) {
497            LOG.debug("resetExtractedCreditMemo() started");
498            if (CreditMemoStatuses.CANCELLED_STATUSES.contains(cmDocument.getStatusCode())) {
499                LOG.debug("resetExtractedCreditMemo() ended");
500                return;
501            }
502            cmDocument.setExtractedTimestamp(null);
503            cmDocument.setCreditMemoPaidTimestamp(null);
504    
505            Note noteObj;
506            try {
507                noteObj = documentService.createNoteFromDocument(cmDocument, note);
508                documentService.addNoteToDocument(cmDocument, noteObj);
509            }
510            catch (Exception e) {
511                throw new RuntimeException(e.getMessage());
512            }
513            purapService.saveDocumentNoValidation(cmDocument);
514    
515            LOG.debug("resetExtractedCreditMemo() CM " + cmDocument.getPurapDocumentIdentifier() + " Cancelled Without Workflow");
516            LOG.debug("resetExtractedCreditMemo() ended");
517        }
518    
519        /**
520         * @see org.kuali.kfs.module.purap.document.service.AccountsPayableDocumentSpecificService#shouldPurchaseOrderBeReversed(org.kuali.kfs.module.purap.document.AccountsPayableDocument)
521         */
522        public boolean shouldPurchaseOrderBeReversed(AccountsPayableDocument apDoc) {
523            // always return false, never reverse
524            return false;
525        }
526    
527        /**
528         * @see org.kuali.kfs.module.purap.document.service.AccountsPayableDocumentSpecificService#getPersonForCancel(org.kuali.kfs.module.purap.document.AccountsPayableDocument)
529         */
530        public Person getPersonForCancel(AccountsPayableDocument apDoc) {
531            // return null, since superuser is fine for CM
532            return null;
533        }
534    
535        /**
536         * @see org.kuali.kfs.module.purap.document.service.AccountsPayableDocumentSpecificService#takePurchaseOrderCancelAction(org.kuali.kfs.module.purap.document.AccountsPayableDocument)
537         */
538        public void takePurchaseOrderCancelAction(AccountsPayableDocument apDoc) {
539            VendorCreditMemoDocument cmDocument = (VendorCreditMemoDocument) apDoc;
540            if (cmDocument.isReopenPurchaseOrderIndicator()) {
541                String docType = PurapConstants.PurchaseOrderDocTypes.PURCHASE_ORDER_CLOSE_DOCUMENT;
542                purchaseOrderService.createAndRoutePotentialChangeDocument(cmDocument.getPurchaseOrderDocument().getDocumentNumber(), docType, "reopened by Payment Request " + apDoc.getPurapDocumentIdentifier() + "cancel", new ArrayList(), PurapConstants.PurchaseOrderStatuses.PENDING_CLOSE);
543            }
544        }
545    
546        /**
547         * @see org.kuali.kfs.module.purap.document.service.CreditMemoService#markPaid(org.kuali.kfs.module.purap.document.CreditMemoDocument,
548         *      java.sql.Date)
549         */
550        public void markPaid(VendorCreditMemoDocument cm, Date processDate) {
551            LOG.debug("markPaid() started");
552    
553            cm.setCreditMemoPaidTimestamp(new Timestamp(processDate.getTime()));
554            purapService.saveDocumentNoValidation(cm);
555        }
556    
557        /**
558         * @see org.kuali.kfs.module.purap.document.service.AccountsPayableDocumentSpecificService#poItemEligibleForAp(org.kuali.kfs.module.purap.document.AccountsPayableDocument, org.kuali.kfs.module.purap.businessobject.PurchaseOrderItem)
559         */
560        public boolean poItemEligibleForAp(AccountsPayableDocument apDoc, PurchaseOrderItem poItem) {
561            // if the po item is not active... skip it
562            if (!poItem.isItemActiveIndicator()) {
563                return false;
564            }
565    
566            if (poItem.getItemType().isQuantityBasedGeneralLedgerIndicator() && poItem.getItemInvoicedTotalQuantity().isGreaterThan(KualiDecimal.ZERO)) {
567                return true;
568            }
569            else {
570                BigDecimal unitPrice = (poItem.getItemUnitPrice() == null ? new BigDecimal(0) : poItem.getItemUnitPrice());
571                if (unitPrice.doubleValue() > poItem.getItemOutstandingEncumberedAmount().doubleValue()) {
572                    return true;
573                }
574            }
575            return false;
576        }
577        
578        /**
579         * The given document here needs to be a Credit Memo.
580         * 
581         * @see org.kuali.kfs.module.purap.document.service.AccountsPayableDocumentSpecificService#generateGLEntriesCreateAccountsPayableDocument(org.kuali.kfs.module.purap.document.AccountsPayableDocument)
582         */
583        public void generateGLEntriesCreateAccountsPayableDocument(AccountsPayableDocument apDocument) {
584            VendorCreditMemoDocument creditMemo = (VendorCreditMemoDocument)apDocument;
585            purapGeneralLedgerService.generateEntriesCreateCreditMemo(creditMemo);
586        }
587    
588        /**
589         * Records the specified error message into the Log file and throws a runtime exception.
590         * 
591         * @param errorMessage the error message to be logged.
592         */
593        protected void logAndThrowRuntimeException(String errorMessage) {
594            this.logAndThrowRuntimeException(errorMessage, null);
595        }
596    
597        /**
598         * Records the specified error message into the Log file and throws the specified runtime exception.
599         * 
600         * @param errorMessage the specified error message.
601         * @param e the specified runtime exception.
602         */
603        protected void logAndThrowRuntimeException(String errorMessage, Exception e) {
604            if (ObjectUtils.isNotNull(e)) {
605                LOG.error(errorMessage, e);
606                throw new RuntimeException(errorMessage, e);
607            }
608            else {
609                LOG.error(errorMessage);
610                throw new RuntimeException(errorMessage);
611            }
612        }
613    
614        /**
615         * @see org.kuali.kfs.module.purap.document.service.CreditMemoService#hasActiveCreditMemosForPurchaseOrder(java.lang.Integer)
616         */
617        public boolean hasActiveCreditMemosForPurchaseOrder(Integer purchaseOrderIdentifier){
618            
619            boolean hasActiveCreditMemos = false;
620            List<String> docNumbers= null;
621            KualiWorkflowDocument workflowDocument = null;
622            
623            docNumbers= creditMemoDao.getActiveCreditMemoDocumentNumbersForPurchaseOrder(purchaseOrderIdentifier);
624            
625            for (String docNumber : docNumbers) {
626                try{
627                    workflowDocument = workflowDocumentService.createWorkflowDocument(Long.valueOf(docNumber), GlobalVariables.getUserSession().getPerson());
628                }catch(WorkflowException we){
629                    throw new RuntimeException(we);
630                }
631                
632                //if the document is not in a non-active status then return true and stop evaluation
633                if(!(workflowDocument.stateIsCanceled() ||
634                        workflowDocument.stateIsException() ||
635                        workflowDocument.stateIsFinal()) ){
636                    hasActiveCreditMemos = true;
637                    break;
638                }
639    
640            }
641            
642            return hasActiveCreditMemos;
643        }
644    
645        /**
646         * @see org.kuali.kfs.module.purap.document.service.CreditMemoCreateService#populateDocumentAfterInit(org.kuali.kfs.module.purap.document.CreditMemoDocument)
647         */
648        public void populateDocumentAfterInit(VendorCreditMemoDocument cmDocument) {
649    
650            // make a call to search for expired/closed accounts
651            HashMap<String, ExpiredOrClosedAccountEntry> expiredOrClosedAccountList = accountsPayableService.getExpiredOrClosedAccountList(cmDocument);
652    
653            if (cmDocument.isSourceDocumentPaymentRequest()) {
654                populateDocumentFromPreq(cmDocument, expiredOrClosedAccountList);
655            }
656            else if (cmDocument.isSourceDocumentPurchaseOrder()) {
657                populateDocumentFromPO(cmDocument, expiredOrClosedAccountList);
658            }
659            else {
660                populateDocumentFromVendor(cmDocument);
661            }
662    
663            populateDocumentDescription(cmDocument);
664    
665            // write a note for expired/closed accounts if any exist and add a message stating there were expired/closed accounts at the
666            // top of the document
667            accountsPayableService.generateExpiredOrClosedAccountNote(cmDocument, expiredOrClosedAccountList);
668    
669            // set indicator so a message is displayed for accounts that were replaced due to expired/closed status
670            if (ObjectUtils.isNotNull(expiredOrClosedAccountList) && !expiredOrClosedAccountList.isEmpty()) {
671                cmDocument.setContinuationAccountIndicator(true);
672            }
673    
674        }   
675    
676        /**
677         * Populate Credit Memo of type Payment Request.
678         * 
679         * @param cmDocument - Credit Memo Document to Populate
680         */
681        protected void populateDocumentFromPreq(VendorCreditMemoDocument cmDocument, HashMap<String, ExpiredOrClosedAccountEntry> expiredOrClosedAccountList) {
682            PaymentRequestDocument paymentRequestDocument = paymentRequestService.getPaymentRequestById(cmDocument.getPaymentRequestIdentifier());
683            cmDocument.getDocumentHeader().setOrganizationDocumentNumber(paymentRequestDocument.getDocumentHeader().getOrganizationDocumentNumber());
684            cmDocument.setPaymentRequestDocument(paymentRequestDocument);
685            cmDocument.setPurchaseOrderDocument(paymentRequestDocument.getPurchaseOrderDocument());
686            cmDocument.setUseTaxIndicator(paymentRequestDocument.isUseTaxIndicator());
687            
688            // credit memo address taken directly from payment request
689            cmDocument.setVendorHeaderGeneratedIdentifier(paymentRequestDocument.getVendorHeaderGeneratedIdentifier());
690            cmDocument.setVendorDetailAssignedIdentifier(paymentRequestDocument.getVendorDetailAssignedIdentifier());
691            cmDocument.setVendorAddressGeneratedIdentifier(paymentRequestDocument.getVendorAddressGeneratedIdentifier());
692            cmDocument.setVendorCustomerNumber(paymentRequestDocument.getVendorCustomerNumber());
693            cmDocument.setVendorName(paymentRequestDocument.getVendorName());
694            cmDocument.setVendorLine1Address(paymentRequestDocument.getVendorLine1Address());
695            cmDocument.setVendorLine2Address(paymentRequestDocument.getVendorLine2Address());
696            cmDocument.setVendorCityName(paymentRequestDocument.getVendorCityName());
697            cmDocument.setVendorStateCode(paymentRequestDocument.getVendorStateCode());
698            cmDocument.setVendorPostalCode(paymentRequestDocument.getVendorPostalCode());
699            cmDocument.setVendorCountryCode(paymentRequestDocument.getVendorCountryCode());
700            cmDocument.setVendorAttentionName(paymentRequestDocument.getVendorAttentionName());
701            cmDocument.setAccountsPayablePurchasingDocumentLinkIdentifier(paymentRequestDocument.getAccountsPayablePurchasingDocumentLinkIdentifier());
702    
703            // prep the item lines (also collect warnings for later display) this is only done on paymentRequest
704            purapAccountingService.convertMoneyToPercent(paymentRequestDocument);
705            populateItemLinesFromPreq(cmDocument, expiredOrClosedAccountList);
706        }
707    
708        /**
709         * Populates the credit memo items from the payment request items.
710         * 
711         * @param cmDocument - Credit Memo Document to Populate
712         */
713        protected void populateItemLinesFromPreq(VendorCreditMemoDocument cmDocument, HashMap<String, ExpiredOrClosedAccountEntry> expiredOrClosedAccountList) {
714            PaymentRequestDocument preqDocument = cmDocument.getPaymentRequestDocument();
715    
716            for (PaymentRequestItem preqItemToTemplate : (List<PaymentRequestItem>) preqDocument.getItems()) {
717                if (preqItemToTemplate.getItemType().isLineItemIndicator() && ((preqItemToTemplate.getItemType().isQuantityBasedGeneralLedgerIndicator() && preqItemToTemplate.getItemQuantity().isNonZero()) 
718                        || (preqItemToTemplate.getItemType().isAmountBasedGeneralLedgerIndicator() && preqItemToTemplate.getTotalAmount().isNonZero()))) {
719                    cmDocument.getItems().add(new CreditMemoItem(cmDocument, preqItemToTemplate, preqItemToTemplate.getPurchaseOrderItem(), expiredOrClosedAccountList));
720                }
721            }
722    
723            // add below the line items
724            purapService.addBelowLineItems(cmDocument);
725            
726            cmDocument.fixItemReferences();
727        }
728    
729        /**
730         * Populate Credit Memo of type Purchase Order.
731         * 
732         * @param cmDocument - Credit Memo Document to Populate
733         */
734        protected void populateDocumentFromPO(VendorCreditMemoDocument cmDocument, HashMap<String, ExpiredOrClosedAccountEntry> expiredOrClosedAccountList) {
735            PurchaseOrderDocument purchaseOrderDocument = purchaseOrderService.getCurrentPurchaseOrder(cmDocument.getPurchaseOrderIdentifier());
736            cmDocument.setPurchaseOrderDocument(purchaseOrderDocument);
737            cmDocument.getDocumentHeader().setOrganizationDocumentNumber(purchaseOrderDocument.getDocumentHeader().getOrganizationDocumentNumber());
738            cmDocument.setUseTaxIndicator(cmDocument.isUseTaxIndicator());
739            
740            cmDocument.setVendorHeaderGeneratedIdentifier(purchaseOrderDocument.getVendorHeaderGeneratedIdentifier());
741            cmDocument.setVendorDetailAssignedIdentifier(purchaseOrderDocument.getVendorDetailAssignedIdentifier());
742            cmDocument.setVendorCustomerNumber(purchaseOrderDocument.getVendorCustomerNumber());
743            cmDocument.setVendorName(purchaseOrderDocument.getVendorName());
744            cmDocument.setAccountsPayablePurchasingDocumentLinkIdentifier(purchaseOrderDocument.getAccountsPayablePurchasingDocumentLinkIdentifier());
745    
746            // populate cm vendor address with the default remit address type for the vendor if found
747            String userCampus = GlobalVariables.getUserSession().getPerson().getCampusCode();
748            VendorAddress vendorAddress = vendorService.getVendorDefaultAddress(purchaseOrderDocument.getVendorHeaderGeneratedIdentifier(), purchaseOrderDocument.getVendorDetailAssignedIdentifier(), VendorConstants.AddressTypes.REMIT, userCampus);
749            if (vendorAddress != null) {
750                cmDocument.templateVendorAddress(vendorAddress);
751                cmDocument.setVendorAddressGeneratedIdentifier(vendorAddress.getVendorAddressGeneratedIdentifier());
752                cmDocument.setVendorAttentionName(StringUtils.defaultString(vendorAddress.getVendorAttentionName()));
753            }
754            else {
755                // set address from PO
756                cmDocument.setVendorAddressGeneratedIdentifier(purchaseOrderDocument.getVendorAddressGeneratedIdentifier());
757                cmDocument.setVendorLine1Address(purchaseOrderDocument.getVendorLine1Address());
758                cmDocument.setVendorLine2Address(purchaseOrderDocument.getVendorLine2Address());
759                cmDocument.setVendorCityName(purchaseOrderDocument.getVendorCityName());
760                cmDocument.setVendorStateCode(purchaseOrderDocument.getVendorStateCode());
761                cmDocument.setVendorPostalCode(purchaseOrderDocument.getVendorPostalCode());
762                cmDocument.setVendorCountryCode(purchaseOrderDocument.getVendorCountryCode());
763                
764                boolean blankAttentionLine = StringUtils.equalsIgnoreCase("Y",SpringContext.getBean(KualiConfigurationService.class).getParameterValue(PurapConstants.PURAP_NAMESPACE, "Document", PurapParameterConstants.BLANK_ATTENTION_LINE_FOR_PO_TYPE_ADDRESS));
765                if (blankAttentionLine){
766                    cmDocument.setVendorAttentionName(StringUtils.EMPTY);
767                }else{
768                    cmDocument.setVendorAttentionName(StringUtils.defaultString(purchaseOrderDocument.getVendorAttentionName()));
769                }
770            }
771    
772            populateItemLinesFromPO(cmDocument, expiredOrClosedAccountList);
773        }
774    
775        /**
776         * Populates the credit memo items from the payment request items.
777         * 
778         * @param cmDocument - Credit Memo Document to Populate
779         */
780        protected void populateItemLinesFromPO(VendorCreditMemoDocument cmDocument, HashMap<String, ExpiredOrClosedAccountEntry> expiredOrClosedAccountList) {
781            List<PurchaseOrderItem> invoicedItems = getPOInvoicedItems(cmDocument.getPurchaseOrderDocument());
782            for (PurchaseOrderItem poItem : invoicedItems) {
783                if ((poItem.getItemType().isQuantityBasedGeneralLedgerIndicator() && poItem.getItemInvoicedTotalQuantity().isNonZero())
784                        || (poItem.getItemType().isAmountBasedGeneralLedgerIndicator() && poItem.getItemInvoicedTotalAmount().isNonZero())) {
785                    CreditMemoItem creditMemoItem = new CreditMemoItem(cmDocument, poItem, expiredOrClosedAccountList);
786                    cmDocument.getItems().add(creditMemoItem);
787                    PurchasingCapitalAssetItem purchasingCAMSItem = cmDocument.getPurchaseOrderDocument().getPurchasingCapitalAssetItemByItemIdentifier(poItem.getItemIdentifier());
788                    if (purchasingCAMSItem != null) {
789                        creditMemoItem.setCapitalAssetTransactionTypeCode(purchasingCAMSItem.getCapitalAssetTransactionTypeCode());
790                    }
791                }
792            }
793    
794            // add below the line items
795            purapService.addBelowLineItems(cmDocument);
796            
797            cmDocument.fixItemReferences();
798        }
799    
800        /**
801         * Populate Credit Memo of type Vendor.
802         * 
803         * @param cmDocument - Credit Memo Document to Populate
804         */
805        protected void populateDocumentFromVendor(VendorCreditMemoDocument cmDocument) {
806            Integer vendorHeaderId = VendorUtils.getVendorHeaderId(cmDocument.getVendorNumber());
807            Integer vendorDetailId = VendorUtils.getVendorDetailId(cmDocument.getVendorNumber());
808    
809            VendorDetail vendorDetail = vendorService.getVendorDetail(vendorHeaderId, vendorDetailId);
810            cmDocument.setVendorDetail(vendorDetail);
811            
812            cmDocument.setVendorHeaderGeneratedIdentifier(vendorDetail.getVendorHeaderGeneratedIdentifier());
813            cmDocument.setVendorDetailAssignedIdentifier(vendorDetail.getVendorDetailAssignedIdentifier());
814            cmDocument.setVendorCustomerNumber(vendorDetail.getVendorNumber());
815            cmDocument.setVendorName(vendorDetail.getVendorName());
816    
817    
818            // credit memo type vendor uses the default remit type address for the vendor if found
819            String userCampus = GlobalVariables.getUserSession().getPerson().getCampusCode();
820            VendorAddress vendorAddress = vendorService.getVendorDefaultAddress(vendorHeaderId, vendorDetailId, VendorConstants.AddressTypes.REMIT, userCampus);
821            if (vendorAddress == null) {
822                // pick up the default vendor po address type
823                vendorAddress = vendorService.getVendorDefaultAddress(vendorHeaderId, vendorDetailId, VendorConstants.AddressTypes.PURCHASE_ORDER, userCampus);
824            }
825    
826            cmDocument.setVendorAddressGeneratedIdentifier(vendorAddress.getVendorAddressGeneratedIdentifier());
827            cmDocument.templateVendorAddress(vendorAddress);
828    
829            // add below the line items
830            purapService.addBelowLineItems(cmDocument);        
831        }
832    
833        /**
834         * Defaults the document description based on the credit memo source type.
835         * 
836         * @param cmDocument - Credit Memo Document to Populate
837         */
838        protected void populateDocumentDescription(VendorCreditMemoDocument cmDocument) {
839            String description = "";
840            if (cmDocument.isSourceVendor()) {
841                description = "Vendor: " + cmDocument.getVendorName();
842            }
843            else {
844                description = "PO: " + cmDocument.getPurchaseOrderDocument().getPurapDocumentIdentifier() + " Vendor: " + cmDocument.getVendorName();
845            }
846    
847            // trim description if longer than whats specified in the data dictionary
848            int noteTextMaxLength = dataDictionaryService.getAttributeMaxLength(DocumentHeader.class, KNSPropertyConstants.DOCUMENT_DESCRIPTION).intValue();
849            if (noteTextMaxLength < description.length()) {
850                description = description.substring(0, noteTextMaxLength);
851            }
852    
853            cmDocument.getDocumentHeader().setDocumentDescription(description);
854        }
855    
856    }
857