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.ar.businessobject;
017    
018    import java.math.BigDecimal;
019    import java.sql.Date;
020    import java.util.ArrayList;
021    import java.util.HashMap;
022    import java.util.LinkedHashMap;
023    import java.util.List;
024    import java.util.Map;
025    
026    import org.apache.commons.lang.StringUtils;
027    import org.apache.log4j.Logger;
028    import org.kuali.kfs.coa.businessobject.ObjectCode;
029    import org.kuali.kfs.coa.businessobject.SubObjectCode;
030    import org.kuali.kfs.module.ar.document.CustomerInvoiceDocument;
031    import org.kuali.kfs.module.ar.document.service.CustomerInvoiceWriteoffDocumentService;
032    import org.kuali.kfs.sys.KFSConstants;
033    import org.kuali.kfs.sys.businessobject.SourceAccountingLine;
034    import org.kuali.kfs.sys.businessobject.UnitOfMeasure;
035    import org.kuali.kfs.sys.context.SpringContext;
036    import org.kuali.rice.kew.exception.WorkflowException;
037    import org.kuali.rice.kns.service.BusinessObjectService;
038    import org.kuali.rice.kns.service.DocumentService;
039    import org.kuali.rice.kns.util.KualiDecimal;
040    import org.kuali.rice.kns.util.ObjectUtils;
041    
042    /**
043     * This class represents a customer invoice detail on the customer invoice document. This class extends SourceAccountingLine since
044     * each customer invoice detail has associated accounting line information.
045     * 
046     * @author Kuali Nervous System Team (kualidev@oncourse.iu.edu)
047     */
048    public class CustomerInvoiceDetail extends SourceAccountingLine implements AppliedPayment {
049        private static Logger LOG = Logger.getLogger(CustomerInvoiceDetail.class);
050    
051        // private Integer invoiceItemNumber; using SourceAccountingLine.sequenceNumber
052        private BigDecimal invoiceItemQuantity;
053        private BigDecimal invoiceItemUnitPrice;
054        // private KualiDecimal invoiceItemTotalAmount; using SourceAccountingLine.amount for now
055        private Date invoiceItemServiceDate;
056        private String invoiceItemCode;
057        private String invoiceItemDescription;
058        private String accountsReceivableObjectCode;
059        private String accountsReceivableSubObjectCode;
060        private KualiDecimal invoiceItemTaxAmount;
061        private boolean taxableIndicator;
062        private boolean isDebit;
063        private Integer invoiceItemDiscountLineNumber;
064    
065        private String invoiceItemUnitOfMeasureCode;
066        private UnitOfMeasure unitOfMeasure;
067    
068        private SubObjectCode accountsReceivableSubObject;
069        private ObjectCode accountsReceivableObject;
070    
071        private transient CustomerInvoiceDocument customerInvoiceDocument;
072        private transient CustomerInvoiceDetail parentDiscountCustomerInvoiceDetail;
073        private transient CustomerInvoiceDetail discountCustomerInvoiceDetail;
074    
075        // fields used for CustomerInvoiceWriteoffDocument
076        private KualiDecimal writeoffAmount;
077        private String customerInvoiceWriteoffDocumentNumber;
078    
079        /**
080         * Default constructor.
081         */
082        public CustomerInvoiceDetail() {
083            super();
084            invoiceItemTaxAmount = KualiDecimal.ZERO;
085        }
086        
087        // ---- BEGIN OPEN AMOUNTS
088        
089        public KualiDecimal getAmountOpen() { 
090            
091            //  if the parent isnt saved, or if its saved but not approved, we 
092            // need to include the discounts.  If its both saved AND approved, 
093            // we do not include the discounts.
094            boolean includeDiscounts = !(isParentSaved() && isParentApproved());
095            
096            KualiDecimal amount = getAmount();
097            KualiDecimal applied = getAmountApplied();
098            KualiDecimal a = amount.subtract(applied);
099            
100            if (includeDiscounts) {
101                CustomerInvoiceDetail discount = getDiscountCustomerInvoiceDetail();
102                if (ObjectUtils.isNotNull(discount)) {
103                    a = a.add(discount.getAmount());
104                }
105            }
106            return a;
107        }
108        
109        private boolean isParentSaved() {
110            return getCustomerInvoiceDocument() != null;
111        }
112        
113        private boolean isParentApproved() {
114            if (getCustomerInvoiceDocument() == null) {
115                return false;
116            }
117            return KFSConstants.DocumentStatusCodes.APPROVED.equalsIgnoreCase(getCustomerInvoiceDocument().getDocumentHeader().getFinancialDocumentStatusCode());
118        }
119        
120        //TODO Andrew
121    //    @Deprecated
122    //    private KualiDecimal getAmountOpenFromDatabaseNoDiscounts() {
123    //        KualiDecimal amount = getAmount();
124    //        KualiDecimal applied = getAmountAppliedFromDatabase();
125    //        KualiDecimal a = amount.subtract(applied);
126    //        return a;
127    //    }
128    //
129    //    @Deprecated
130    //    private KualiDecimal getAmountOpenFromDatabaseDiscounted() {
131    //        KualiDecimal amount = getAmount();
132    //        KualiDecimal applied = getAmountAppliedFromDatabase();
133    //        KualiDecimal a = amount.subtract(applied);
134    //        CustomerInvoiceDetail discount = getDiscountCustomerInvoiceDetail();
135    //        if (ObjectUtils.isNotNull(discount)) {
136    //            a = a.add(discount.getAmount());
137    //        }
138    //        return a;
139    //    }
140    
141        //TODO Andrew
142        //public KualiDecimal getAmountOpenExcludingAnyAmountFromCurrentPaymentApplicationDocument() {
143        //    return getAmountOpenExcludingAnyAmountFrom(getCurrentPaymentApplicationDocument());
144        //}
145        
146        /**
147         * 
148         * Retrieves the discounted amount.  This is the amount minues any 
149         * discounts that might exist.  If no discount exists, then it 
150         * just returns the amount.
151         * 
152         * NOTE this does not subtract PaidApplieds, only discounts.
153         * 
154         * @return
155         */
156        //PAYAPP
157        public KualiDecimal getAmountDiscounted() {
158            KualiDecimal a = getAmount();
159            CustomerInvoiceDetail discount = getDiscountCustomerInvoiceDetail();
160            if(ObjectUtils.isNotNull(discount)) {
161                KualiDecimal d = discount.getAmount();
162                a = a.add(d);
163            }
164            return a;
165        }
166        
167        //TODO Andrew
168    //    public KualiDecimal getAmountOpenExcludingAnyAmountFrom(PaymentApplicationDocument paymentApplicationDocument) {
169    //        return getAmountDiscounted().subtract(getAmountAppliedExcludingAnyAmountAppliedBy(paymentApplicationDocument));
170    //    }
171    //    
172    //    public KualiDecimal getAmountOpenPerCurrentPaymentApplicationDocument() {
173    //        return getAmountDiscounted().subtract(getAmountAppliedByCurrentPaymentApplicationDocument());
174    //    }
175        
176        /**
177         * This method returns the amount that remained unapplied on a given date.
178         * 
179         * @param date
180         * @return
181         */
182        public KualiDecimal getAmountOpenByDateFromDatabase(java.sql.Date date) {
183            return getAmountOpen();
184            //TODO Andrew - need to fix this to actually respect the dates
185            //return getAmountOpenByDateFromDatabaseExcludingAnyAmountAppliedByPaymentApplicationDocument(date, null);
186        }
187        
188        public KualiDecimal getAmountOpenByDateFromDatabase(java.util.Date date) {
189            return getAmountOpen();
190            //TODO Andrew - need to fix this to actually respect the dates
191            //return getAmountOpenByDateFromDatabaseExcludingAnyAmountAppliedByPaymentApplicationDocument(new java.sql.Date(date.getTime()),null);
192        }
193        
194        //TODO Andrew
195    //    public KualiDecimal getAmountOpenByDateFromDatabaseExcludingAnyAmountAppliedByPaymentApplicationDocument(java.sql.Date date, PaymentApplicationDocument paymentApplicationDocument) {
196    //        BusinessObjectService businessObjectService = SpringContext.getBean(BusinessObjectService.class);
197    //        
198    //        // Lookup all applied payments as of the given date
199    //        Map<String,Object> criteria = new HashMap<String,Object>();
200    //        criteria.put("invoiceItemNumber", getSequenceNumber());
201    //        criteria.put("financialDocumentReferenceInvoiceNumber", getDocumentNumber());
202    //        criteria.put("documentHeader.financialDocumentStatusCode", KFSConstants.DocumentStatusCodes.APPROVED);
203    //        Collection<InvoicePaidApplied> invoicePaidAppliedsAsOfDate = businessObjectService.findMatching(InvoicePaidApplied.class, criteria);
204    //        
205    //        KualiDecimal totalAppliedAmount = new KualiDecimal(0);
206    //        KualiDecimal appliedAmount = new KualiDecimal(0);
207    //        for (InvoicePaidApplied invoicePaidApplied : invoicePaidAppliedsAsOfDate) {
208    //            appliedAmount = invoicePaidApplied.getInvoiceItemAppliedAmount();
209    //            Date invoicePaidDate = invoicePaidApplied.getDocumentHeader().getDocumentFinalDate();
210    //            // get the paid date and use that to limit the adds from below
211    //            if (ObjectUtils.isNotNull(appliedAmount)&&!invoicePaidDate.after(date)) {
212    //                if(null != paymentApplicationDocument) {
213    //                    if(!invoicePaidApplied.getDocumentNumber().equals(paymentApplicationDocument.getDocumentNumber())) {
214    //                        totalAppliedAmount = totalAppliedAmount.add(appliedAmount);
215    //                    }
216    //                } else {
217    //                    totalAppliedAmount = totalAppliedAmount.add(appliedAmount);
218    //                }
219    //            }
220    //        }
221    //        
222    //        return getAmount().subtract(totalAppliedAmount);
223    //    }
224    //    
225    //    public KualiDecimal getAmountOpenByDateFromDatabaseExcludingAnyAmountAppliedByPaymentApplicationDocument(java.util.Date date, PaymentApplicationDocument paymentApplicationDocument) {
226    //        return getAmountOpenByDateFromDatabaseExcludingAnyAmountAppliedByPaymentApplicationDocument(new java.sql.Date(date.getTime()),paymentApplicationDocument);
227    //    }
228        
229        // ---- END OPEN AMOUNTS
230        
231        // ---- BEGIN APPLIED AMOUNTS
232        public KualiDecimal getAmountApplied() { 
233            List<InvoicePaidApplied> invoicePaidApplieds = null;
234            invoicePaidApplieds = getMatchingInvoicePaidAppliedsMatchingAnyDocumentFromDatabase();
235            KualiDecimal appliedAmount = new KualiDecimal(0);
236            for(InvoicePaidApplied invoicePaidApplied : invoicePaidApplieds) {
237                appliedAmount = appliedAmount.add(invoicePaidApplied.getInvoiceItemAppliedAmount());
238            }
239            return appliedAmount;
240        }
241        
242        //TODO Andrew
243    //    /**
244    //     * @return the applied amount by getting it from the matching invoice paid applied
245    //     */
246    //    public KualiDecimal getAmountAppliedFromDatabase() {
247    //        return getAmountAppliedBy(null);
248    //    }
249    //    
250    //  /**
251    //  * This method is a convenience method used from the Struts form on the payment application document screen.
252    //  * @return
253    //  */
254    // public KualiDecimal getAmountAppliedByCurrentPaymentApplicationDocument() {
255    //     return getAmountAppliedBy(getCurrentPaymentApplicationDocument());
256    // }
257    // 
258        /**
259         * @param paymentApplicationDocument
260         * @return 
261         */
262        public KualiDecimal getAmountAppliedBy(String documentNumber) {
263            List<InvoicePaidApplied> invoicePaidApplieds = null;
264            if (StringUtils.isBlank(documentNumber)) {
265                invoicePaidApplieds = getMatchingInvoicePaidAppliedsMatchingAnyDocumentFromDatabase();
266            } else {
267                invoicePaidApplieds = getMatchingInvoicePaidAppliedsMatchingDocument(documentNumber);
268            }
269            KualiDecimal appliedAmount = new KualiDecimal(0);
270            for(InvoicePaidApplied invoicePaidApplied : invoicePaidApplieds) {
271                appliedAmount = appliedAmount.add(invoicePaidApplied.getInvoiceItemAppliedAmount());
272            }
273            return appliedAmount;
274        }
275        
276        /**
277         * @param paymentApplicationDocument
278         * @return the sum of applied amounts according to the database, excluding any amounts applied by paymentApplicationDocument
279         */
280        public KualiDecimal getAmountAppliedExcludingAnyAmountAppliedBy(String documentNumber) {
281            List<InvoicePaidApplied> invoicePaidApplieds = getMatchingInvoicePaidAppliedsMatchingAnyDocumentFromDatabase();
282            KualiDecimal appliedAmount = new KualiDecimal(0);
283            for (InvoicePaidApplied invoicePaidApplied : invoicePaidApplieds) {
284                // Exclude any amounts applied by paymentApplicationDocument
285                if (StringUtils.isNotBlank(documentNumber)) {
286                }
287                if (StringUtils.isBlank(documentNumber) || !documentNumber.equalsIgnoreCase(invoicePaidApplied.getDocumentNumber())) {
288                    appliedAmount = appliedAmount.add(invoicePaidApplied.getInvoiceItemAppliedAmount());
289                }
290            }
291            return appliedAmount;
292        }
293        
294        // ---- END APPLIED AMOUNTS
295        
296        /**
297         * This method returns the writeoff amount. If writeoff document hasn't been approved yet, display the open amount. Else display
298         * the amount applied from the specific approved writeoff document.
299         * 
300         * @param customerInvoiceWriteoffDocumentNumber
301         * @return
302         */
303        public KualiDecimal getWriteoffAmount() {
304            if (SpringContext.getBean(CustomerInvoiceWriteoffDocumentService.class).isCustomerInvoiceWriteoffDocumentApproved(customerInvoiceWriteoffDocumentNumber)) {
305                //TODO this probably isnt right ... in the case of discounts and/or credit 
306                //     memos, the getAmount() isnt the amount that the writeoff document will have 
307                //     written off
308                return super.getAmount(); // using the accounting line amount ... see comments at top of class
309            }
310            else {
311                return getAmountOpen();
312            }
313        }
314        
315        /**
316         * This method returns the invoice pre tax amount
317         * 
318         * @return
319         */
320        public KualiDecimal getInvoiceItemPreTaxAmount() {
321            if (ObjectUtils.isNotNull(invoiceItemUnitPrice) && ObjectUtils.isNotNull(invoiceItemQuantity)) {
322                BigDecimal bd = invoiceItemUnitPrice.multiply(invoiceItemQuantity);
323                bd = bd.setScale(KualiDecimal.SCALE, KualiDecimal.ROUND_BEHAVIOR);
324                return new KualiDecimal(bd);
325            } else {
326                return KualiDecimal.ZERO;
327            }
328        }
329    
330        /**
331         * Gets the accountsReceivableObjectCode attribute.
332         * 
333         * @return Returns the accountsReceivableObjectCode
334         */
335        public String getAccountsReceivableObjectCode() {
336            return accountsReceivableObjectCode;
337        }
338    
339        /**
340         * Sets the accountsReceivableObjectCode attribute.
341         * 
342         * @param accountsReceivableObjectCode The accountsReceivableObjectCode to set.
343         */
344        public void setAccountsReceivableObjectCode(String accountsReceivableObjectCode) {
345            this.accountsReceivableObjectCode = accountsReceivableObjectCode;
346        }
347    
348        /**
349         * Gets the accountsReceivableSubObjectCode attribute.
350         * 
351         * @return Returns the accountsReceivableSubObjectCode
352         */
353        public String getAccountsReceivableSubObjectCode() {
354            return accountsReceivableSubObjectCode;
355        }
356    
357        /**
358         * Sets the accountsReceivableSubObjectCode attribute.
359         * 
360         * @param accountsReceivableSubObjectCode The accountsReceivableSubObjectCode to set.
361         */
362        public void setAccountsReceivableSubObjectCode(String accountsReceivableSubObjectCode) {
363            this.accountsReceivableSubObjectCode = accountsReceivableSubObjectCode;
364        }
365    
366        /**
367         * Gets the invoiceItemQuantity attribute.
368         * 
369         * @return Returns the invoiceItemQuantity
370         */
371        public BigDecimal getInvoiceItemQuantity() {
372            return invoiceItemQuantity;
373        }
374    
375        /**
376         * Sets the invoiceItemQuantity attribute.
377         * 
378         * @param invoiceItemQuantity The invoiceItemQuantity to set.
379         */
380        public void setInvoiceItemQuantity(BigDecimal invoiceItemQuantity) {
381            this.invoiceItemQuantity = invoiceItemQuantity;
382        }
383    
384        /**
385         * Gets the invoiceItemUnitOfMeasureCode attribute.
386         * 
387         * @return Returns the invoiceItemUnitOfMeasureCode
388         */
389        public String getInvoiceItemUnitOfMeasureCode() {
390            return invoiceItemUnitOfMeasureCode;
391        }
392    
393        /**
394         * Sets the invoiceItemUnitOfMeasureCode attribute.
395         * 
396         * @param invoiceItemUnitOfMeasureCode The invoiceItemUnitOfMeasureCode to set.
397         */
398        public void setInvoiceItemUnitOfMeasureCode(String invoiceItemUnitOfMeasureCode) {
399            this.invoiceItemUnitOfMeasureCode = invoiceItemUnitOfMeasureCode;
400        }
401    
402        /**
403         * Gets the invoiceItemUnitPrice attribute.
404         * 
405         * @return Returns the invoiceItemUnitPrice
406         */
407        public BigDecimal getInvoiceItemUnitPrice() {
408            return invoiceItemUnitPrice;
409        }
410    
411        /**
412         * @param invoiceItemUnitPrice
413         */
414        public void setInvoiceItemUnitPrice(KualiDecimal invoiceItemUnitPrice) {
415            if (ObjectUtils.isNotNull(invoiceItemUnitPrice)) {
416                this.invoiceItemUnitPrice = invoiceItemUnitPrice.bigDecimalValue();
417            }
418            else {
419                this.invoiceItemUnitPrice = BigDecimal.ZERO;
420            }
421        }
422    
423        /**
424         * Sets the invoiceItemUnitPrice attribute.
425         * 
426         * @param invoiceItemUnitPrice The invoiceItemUnitPrice to set.
427         */
428        public void setInvoiceItemUnitPrice(BigDecimal invoiceItemUnitPrice) {
429            this.invoiceItemUnitPrice = invoiceItemUnitPrice;
430        }
431    
432        /**
433         * Gets the invoiceItemServiceDate attribute.
434         * 
435         * @return Returns the invoiceItemServiceDate
436         */
437        public Date getInvoiceItemServiceDate() {
438            return invoiceItemServiceDate;
439        }
440    
441        /**
442         * Sets the invoiceItemServiceDate attribute.
443         * 
444         * @param invoiceItemServiceDate The invoiceItemServiceDate to set.
445         */
446        public void setInvoiceItemServiceDate(Date invoiceItemServiceDate) {
447            this.invoiceItemServiceDate = invoiceItemServiceDate;
448        }
449    
450        /**
451         * Gets the invoiceItemCode attribute.
452         * 
453         * @return Returns the invoiceItemCode
454         */
455        public String getInvoiceItemCode() {
456            return invoiceItemCode;
457        }
458    
459        /**
460         * Sets the invoiceItemCode attribute.
461         * 
462         * @param invoiceItemCode The invoiceItemCode to set.
463         */
464        public void setInvoiceItemCode(String invoiceItemCode) {
465            this.invoiceItemCode = invoiceItemCode;
466        }
467    
468        /**
469         * Gets the invoiceItemDescription attribute.
470         * 
471         * @return Returns the invoiceItemDescription
472         */
473        public String getInvoiceItemDescription() {
474            return invoiceItemDescription;
475        }
476    
477        /**
478         * Sets the invoiceItemDescription attribute.
479         * 
480         * @param invoiceItemDescription The invoiceItemDescription to set.
481         */
482        public void setInvoiceItemDescription(String invoiceItemDescription) {
483            this.invoiceItemDescription = invoiceItemDescription;
484        }
485    
486        /**
487         * Gets the invoiceItemTaxAmount attribute. TODO Use tax service to get invoice item tax amount
488         * 
489         * @return Returns the invoiceItemTaxAmount.
490         */
491        public KualiDecimal getInvoiceItemTaxAmount() {
492            return invoiceItemTaxAmount;
493        }
494    
495        /**
496         * Sets the invoiceItemTaxAmount attribute value.
497         * 
498         * @param invoiceItemTaxAmount The invoiceItemTaxAmount to set.
499         */
500        public void setInvoiceItemTaxAmount(KualiDecimal invoiceItemTaxAmount) {
501            this.invoiceItemTaxAmount = invoiceItemTaxAmount;
502        }
503    
504        /**
505         * Gets the invoiceItemDiscountLineNumber attribute.
506         * 
507         * @return Returns the invoiceItemDiscountLineNumber.
508         */
509        public Integer getInvoiceItemDiscountLineNumber() {
510            return invoiceItemDiscountLineNumber;
511        }
512    
513        /**
514         * Sets the invoiceItemDiscountLineNumber attribute value.
515         * 
516         * @param invoiceItemDiscountLineNumber The invoiceItemDiscountLineNumber to set.
517         */
518        public void setInvoiceItemDiscountLineNumber(Integer invoiceItemDiscountLineNumber) {
519            this.invoiceItemDiscountLineNumber = invoiceItemDiscountLineNumber;
520        }
521    
522        /**
523         * Gets the accountsReceivableSubObject attribute.
524         * 
525         * @return Returns the accountsReceivableSubObject
526         */
527        public SubObjectCode getAccountsReceivableSubObject() {
528            return accountsReceivableSubObject;
529        }
530    
531        /**
532         * Sets the accountsReceivableSubObject attribute.
533         * 
534         * @param accountsReceivableSubObject The accountsReceivableSubObject to set.
535         * @deprecated
536         */
537        public void setAccountsReceivableSubObject(SubObjectCode accountsReceivableSubObject) {
538            this.accountsReceivableSubObject = accountsReceivableSubObject;
539        }
540    
541        /**
542         * Gets the accountsReceivableObject attribute.
543         * 
544         * @return Returns the accountsReceivableObject
545         */
546        public ObjectCode getAccountsReceivableObject() {
547            return accountsReceivableObject;
548        }
549    
550        /**
551         * Sets the accountsReceivableObject attribute.
552         * 
553         * @param accountsReceivableObject The accountsReceivableObject to set.
554         * @deprecated
555         */
556        public void setAccountsReceivableObject(ObjectCode accountsReceivableObject) {
557            this.accountsReceivableObject = accountsReceivableObject;
558        }
559    
560        /**
561         * @see org.kuali.rice.kns.bo.BusinessObjectBase#toStringMapper()
562         */
563        @SuppressWarnings("unchecked")
564        protected LinkedHashMap toStringMapper() {
565            LinkedHashMap m = new LinkedHashMap();
566            m.put("documentNumber", getDocumentNumber());
567            if (this.getSequenceNumber() != null) {
568                m.put("invoiceItemNumber", this.getSequenceNumber().toString());
569            }
570            return m;
571        }
572    
573        /**
574         * Update line amount based on quantity and unit price
575         */
576        public void updateAmountBasedOnQuantityAndUnitPrice() {
577            setAmount(getInvoiceItemPreTaxAmount());
578        }
579    
580        public boolean isTaxableIndicator() {
581            return taxableIndicator;
582        }
583    
584        // yes this is redundant, its required for the JSP on the accounting
585        // line checkbox field
586        public boolean getTaxableIndicator() {
587            return taxableIndicator;
588        }
589    
590        public void setTaxableIndicator(boolean taxableIndicator) {
591            this.taxableIndicator = taxableIndicator;
592        }
593    
594        public boolean isDebit() {
595            return isDebit;
596        }
597    
598        public void setDebit(boolean isDebit) {
599            this.isDebit = isDebit;
600        }
601    
602        /**
603         * This method returns true if customer invoice detail has a corresponding discount line
604         * 
605         * @return
606         */
607        public boolean isDiscountLineParent() {
608            return ObjectUtils.isNotNull(getInvoiceItemDiscountLineNumber());
609        }
610    
611        /**
612         * This method should only be used to determine if detail is discount line in JSP. If you want to determine if invoice detail is
613         * a detail line use CustomerInvoiceDocument.isDiscountLineBasedOnSequenceNumber() instead.
614         * 
615         * @return
616         */
617        public boolean isDiscountLine() {
618            return ObjectUtils.isNotNull(parentDiscountCustomerInvoiceDetail);
619        }
620    
621        /**
622         * This method sets the amount to negative if it isn't already negative
623         * 
624         * @return
625         */
626        public void setInvoiceItemUnitPriceToNegative() {
627            // if unit price is positive
628            if (invoiceItemUnitPrice.compareTo(BigDecimal.ZERO) == 1) {
629                invoiceItemUnitPrice = invoiceItemUnitPrice.negate();
630            }
631        }
632    
633        public CustomerInvoiceDetail getParentDiscountCustomerInvoiceDetail() {
634            return parentDiscountCustomerInvoiceDetail;
635        }
636    
637        public void setParentDiscountCustomerInvoiceDetail(CustomerInvoiceDetail parentDiscountCustomerInvoiceDetail) {
638            this.parentDiscountCustomerInvoiceDetail = parentDiscountCustomerInvoiceDetail;
639        }
640    
641        public CustomerInvoiceDetail getDiscountCustomerInvoiceDetail() {
642            return discountCustomerInvoiceDetail;
643        }
644    
645        public void setDiscountCustomerInvoiceDetail(CustomerInvoiceDetail discountCustomerInvoiceDetail) {
646            this.discountCustomerInvoiceDetail = discountCustomerInvoiceDetail;
647        }
648    
649        // ---- Methods to find matching InvoicePaidApplieds.
650        
651        /**
652         * @return matching InvoicePaidApplieds from the database if they exist
653         */
654        public List<InvoicePaidApplied> getMatchingInvoicePaidAppliedsMatchingAnyDocumentFromDatabase() {
655            //TODO Andrew
656            //return getMatchingInvoicePaidApplieds(null);
657    
658            BusinessObjectService businessObjectService = SpringContext.getBean(BusinessObjectService.class);
659    
660            // assuming here that you never have a PaidApplied against a Discount line
661            Map<String,Object> criteria = new HashMap<String,Object>();
662            criteria.put("invoiceItemNumber", getInvoiceItemNumber());
663            criteria.put("financialDocumentReferenceInvoiceNumber", getDocumentNumber());
664            criteria.put("documentHeader.financialDocumentStatusCode", KFSConstants.DocumentStatusCodes.APPROVED);
665    
666            List<InvoicePaidApplied> invoicePaidApplieds = (List<InvoicePaidApplied>) businessObjectService.findMatching(InvoicePaidApplied.class, criteria);
667            if(ObjectUtils.isNull(invoicePaidApplieds)) {
668                invoicePaidApplieds = new ArrayList<InvoicePaidApplied>();
669            }
670            return invoicePaidApplieds;
671        }
672        
673        /**
674         * @param paymentApplicationDocumentNumber
675         * @return the List of matching InvoicePaidApplieds.
676         * If paymentApplicationDocumentNumber is null invoicePaidApplieds matching any PaymentApplicationDocument will be returned.
677         * If paymentApplicationDocumentNumber is not null only the invoicePaidApplieds that match on that PaymentApplicationDocument will be returned.
678         */
679        private List<InvoicePaidApplied> getMatchingInvoicePaidAppliedsMatchingDocument(String documentNumber) {
680            if (StringUtils.isBlank(documentNumber)) {
681                return getMatchingInvoicePaidAppliedsMatchingAnyDocumentFromDatabase();
682            }
683            
684            BusinessObjectService businessObjectService = SpringContext.getBean(BusinessObjectService.class);
685            Map<String,Object> criteria = new HashMap<String,Object>();
686            criteria.put("documentNumber", documentNumber);
687            criteria.put("invoiceItemNumber", getSequenceNumber());
688            criteria.put("financialDocumentReferenceInvoiceNumber", getDocumentNumber());
689    
690            List<InvoicePaidApplied> invoicePaidApplieds = (List<InvoicePaidApplied>) businessObjectService.findMatching(InvoicePaidApplied.class, criteria);
691            if(ObjectUtils.isNull(invoicePaidApplieds)) {
692                invoicePaidApplieds = new ArrayList<InvoicePaidApplied>();
693            }
694            return invoicePaidApplieds;
695        }
696    
697        //TODO Andrew
698    //    /**
699    //     * @param paymentApplicationDocumentNumber
700    //     * @return matching InvoicePaidApplieds from the database matching the specific PaymentApplicationDocument if they exist
701    //     */
702    //    public List<InvoicePaidApplied> getMatchingInvoicePaidAppliedsMatchingASpecificPaymentApplicationDocumentFromDatabase(String paymentApplicationDocumentNumber) {
703    //        return getMatchingInvoicePaidApplieds(paymentApplicationDocumentNumber);
704    //    }
705    //
706    //    /**
707    //     * @param paymentApplicationDocumentNumber
708    //     * @return
709    //     */
710    //    public InvoicePaidApplied getSingleMatchingInvoicePaidAppliedMatchingASpecificPaymentApplicationDocumentFromDatabase(String paymentApplicationDocumentNumber) {
711    //        List<InvoicePaidApplied> matchingInvoicePaidApplieds = getMatchingInvoicePaidApplieds(paymentApplicationDocumentNumber);
712    //        return matchingInvoicePaidApplieds.iterator().next();
713    //    }
714    //    
715    //    
716    //    /**
717    //     * Get InvoicePaidApplieds related to this CustomerInvoiceDetail if they 
718    //     * exist in a PaymentApplicationDocument and do it just by looking at the
719    //     * PaymentApplicationDocument as it is in memory. Don't get anything from
720    //     * the database.
721    //     * 
722    //     * @param paymentApplicationDocument
723    //     * @return
724    //     */
725    //    public List<InvoicePaidApplied> getMatchingInvoicePaidAppliedsMatchingPaymentApplicationDocumentNoDatabase(PaymentApplicationDocument paymentApplicationDocument) {
726    //        List<InvoicePaidApplied> invoicePaidApplieds = paymentApplicationDocument.getInvoicePaidApplieds();
727    //        List<InvoicePaidApplied> selectedInvoicePaidApplieds = new ArrayList<InvoicePaidApplied>();
728    //        // The paymentApplicationDocumentService isn't used to pull anything from the database.
729    //        // It's just used to try to pair CustomerInvoiceDetails and InvoicePaidApplieds
730    //        PaymentApplicationDocumentService paymentApplicationDocumentService = SpringContext.getBean(PaymentApplicationDocumentService.class);
731    //        for(InvoicePaidApplied invoicePaidApplied : invoicePaidApplieds) {
732    //            if(paymentApplicationDocumentService.customerInvoiceDetailPairsWithInvoicePaidApplied(this, invoicePaidApplied)) {
733    //                selectedInvoicePaidApplieds.add(invoicePaidApplied);
734    //            }
735    //        }
736    //        return selectedInvoicePaidApplieds;
737    //    }
738    //    
739    //    /**
740    //     * This method returns the results of @link getMatchingInvoicePaidAppliedsMatchingPaymentApplicationDocumentNoDatabase(PaymentApplicationDocument)
741    //     * as a single InvoicePaidApplied. This is OK because there's only one
742    //     * InvoicePaidApplied for a CustomerInvoiceDetail on a given PaymentApplicationDocument.
743    //     * 
744    //     * @param paymentApplicationDocument
745    //     * @return
746    //     */
747    //    public InvoicePaidApplied getSingleMatchingInvoicepaidAppliedMatchingPaymentApplicationDocumentNoDatabase(PaymentApplicationDocument paymentApplicationDocument) {
748    //        List<InvoicePaidApplied> matchingInvoicePaidApplieds = getMatchingInvoicePaidAppliedsMatchingPaymentApplicationDocumentNoDatabase(paymentApplicationDocument);
749    //        return matchingInvoicePaidApplieds.iterator().next();
750    //    }
751    
752        // ---- Simple getters/setters
753        
754        public CustomerInvoiceDocument getCustomerInvoiceDocument() {
755            if (customerInvoiceDocument == null) {
756                DocumentService documentService = (DocumentService) SpringContext.getBean(DocumentService.class);
757                try {
758                    customerInvoiceDocument = (CustomerInvoiceDocument) documentService.getByDocumentHeaderId(getDocumentNumber());
759                }
760                catch (WorkflowException e) {
761                    throw new RuntimeException("A WorkflowException was thrown when trying to open the details parent document.  This should never happen.", e);
762                }
763            }
764            return customerInvoiceDocument;
765        }
766        
767        public void setCustomerInvoiceDocument(CustomerInvoiceDocument customerInvoiceDocument) {
768            this.customerInvoiceDocument = customerInvoiceDocument;
769        }
770        
771        public String getCustomerInvoiceWriteoffDocumentNumber() {
772            return customerInvoiceWriteoffDocumentNumber;
773        }
774    
775        public void setCustomerInvoiceWriteoffDocumentNumber(String customerInvoiceWriteoffDocumentNumber) {
776            this.customerInvoiceWriteoffDocumentNumber = customerInvoiceWriteoffDocumentNumber;
777        }
778    
779        public void setWriteoffAmount(KualiDecimal writeoffAmount) {
780            this.writeoffAmount = writeoffAmount;
781        }
782    
783        public UnitOfMeasure getUnitOfMeasure() {
784            return unitOfMeasure;
785        }
786    
787        public void setUnitOfMeasure(UnitOfMeasure unitOfMeasure) {
788            this.unitOfMeasure = unitOfMeasure;
789        }
790    
791        //TODO Andrew
792    //    public boolean isFullApply() {
793    //        return fullApply;
794    //    }
795    //
796    //    public void setFullApply(boolean fullApply) {
797    //        this.fullApply = fullApply;
798    //    }
799    
800        /**
801         * If the detail is a discount customer invoice detail, return the parent customer invoice detail's sequence number instead
802         * 
803         * @see org.kuali.kfs.module.ar.businessobject.AppliedPayment#getInvoiceItemNumber()
804         */
805        public Integer getInvoiceItemNumber() {
806            if (isDiscountLine()) {
807                return parentDiscountCustomerInvoiceDetail.getSequenceNumber();
808            } else {
809                return this.getSequenceNumber();
810            }
811        }
812    
813        /**
814         * If detail is part of an invoice that is a reversal, return the invoice that is being corrected. Else return the customer
815         * details document number.
816         * 
817         * @see org.kuali.kfs.module.ar.businessobject.AppliedPayment#getInvoiceReferenceNumber()
818         */
819        public String getInvoiceReferenceNumber() {
820            return getDocumentNumber();
821        }
822        
823        /**
824         * 
825         * @see org.kuali.rice.kns.bo.PersistableBusinessObjectBase#refresh()
826         */
827        public void refresh() {
828            super.refresh();
829            this.updateAmountBasedOnQuantityAndUnitPrice();
830        }
831        
832    }