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.validation.impl;
017    
018    import java.util.ArrayList;
019    import java.util.List;
020    
021    import org.kuali.kfs.module.purap.PurapConstants;
022    import org.kuali.kfs.module.purap.PurapKeyConstants;
023    import org.kuali.kfs.module.purap.PurapConstants.ItemFields;
024    import org.kuali.kfs.module.purap.businessobject.PaymentRequestItem;
025    import org.kuali.kfs.module.purap.businessobject.PurApAccountingLine;
026    import org.kuali.kfs.module.purap.businessobject.PurApItem;
027    import org.kuali.kfs.module.purap.document.PaymentRequestDocument;
028    import org.kuali.kfs.module.purap.document.service.PurapService;
029    import org.kuali.kfs.sys.document.validation.GenericValidation;
030    import org.kuali.kfs.sys.document.validation.Validation;
031    import org.kuali.kfs.sys.document.validation.event.AttributedDocumentEvent;
032    import org.kuali.kfs.sys.document.validation.impl.AccountingLineAmountPositiveValidation;
033    import org.kuali.kfs.sys.document.validation.impl.AccountingLineDataDictionaryValidation;
034    import org.kuali.kfs.sys.document.validation.impl.AccountingLineValueAllowedValidation;
035    import org.kuali.kfs.sys.document.validation.impl.AccountingLineValuesAllowedValidationHutch;
036    import org.kuali.kfs.sys.document.validation.impl.BusinessObjectDataDictionaryValidation;
037    import org.kuali.kfs.sys.document.validation.impl.CompositeValidation;
038    import org.kuali.rice.kns.util.GlobalVariables;
039    import org.kuali.rice.kns.util.KualiDecimal;
040    import org.kuali.rice.kns.util.ObjectUtils;
041    
042    public class PaymentRequestProcessItemValidation extends GenericValidation {
043    
044        private PurapService purapService;
045        private PurApItem itemForValidation;
046        private AttributedDocumentEvent event;
047        private CompositeValidation reviewAccountingLineValidation;
048        private PaymentRequestDocument preqDocument;
049        private PurApAccountingLine preqAccountingLine;
050        
051        public boolean validate(AttributedDocumentEvent event) {
052            boolean valid = true;
053            this.event = event;
054            
055            PaymentRequestDocument paymentRequestDocument = (PaymentRequestDocument)event.getDocument();
056            PaymentRequestItem preqItem = (PaymentRequestItem) itemForValidation;
057            
058            valid &= validateEachItem(paymentRequestDocument, preqItem);
059            
060            return valid;
061    
062        }
063    
064        /**
065         * Calls another validate item method and passes an identifier string from the item.
066         * 
067         * @param paymentRequestDocument - payment request document.
068         * @param item
069         * @return
070         */
071        protected boolean validateEachItem(PaymentRequestDocument paymentRequestDocument, PaymentRequestItem item) {
072            boolean valid = true;
073            String identifierString = item.getItemIdentifierString();
074            valid &= validateItem(paymentRequestDocument, item, identifierString);
075            return valid;
076        }
077    
078        /**
079         * Performs validation if full document entry not completed and peforms varying item validation.
080         * Such as, above the line, items without accounts, items with accounts.
081         * 
082         * @param paymentRequestDocument - payment request document
083         * @param item - payment request item
084         * @param identifierString - identifier string used to mark in an error map
085         * @return
086         */
087        public boolean validateItem(PaymentRequestDocument paymentRequestDocument, PaymentRequestItem item, String identifierString) {
088            boolean valid = true;
089            // only run item validations if before full entry
090            if (!purapService.isFullDocumentEntryCompleted(paymentRequestDocument)) {
091                if (item.getItemType().isLineItemIndicator()) {
092                    valid &= validateAboveTheLineItems(item, identifierString,paymentRequestDocument.isReceivingDocumentRequiredIndicator());
093                }
094                valid &= validateItemWithoutAccounts(item, identifierString);
095            }
096            // always run account validations
097            valid &= validateItemAccounts(paymentRequestDocument, item, identifierString);
098            return valid;
099        }
100    
101        /**
102         * Validates above the line items.
103         * 
104         * @param item - payment request item
105         * @param identifierString - identifier string used to mark in an error map
106         * @return
107         */
108        protected boolean validateAboveTheLineItems(PaymentRequestItem item, String identifierString, boolean isReceivingDocumentRequiredIndicator) {
109            boolean valid = true;
110            // Currently Quantity is allowed to be NULL on screen;
111            // must be either a positive number or NULL for DB
112            if (ObjectUtils.isNotNull(item.getItemQuantity())) {
113                if (item.getItemQuantity().isNegative()) {
114                    // if quantity is negative give an error
115                    valid = false;
116                    GlobalVariables.getMessageMap().putError(PurapConstants.ITEM_TAB_ERROR_PROPERTY, PurapKeyConstants.ERROR_ITEM_AMOUNT_BELOW_ZERO, ItemFields.INVOICE_QUANTITY, identifierString);
117                }
118                if (!isReceivingDocumentRequiredIndicator){
119                    if (item.getPoOutstandingQuantity().isLessThan(item.getItemQuantity())) {
120                        valid = false;
121                        GlobalVariables.getMessageMap().putError(PurapConstants.ITEM_TAB_ERROR_PROPERTY, PurapKeyConstants.ERROR_ITEM_QUANTITY_TOO_MANY, ItemFields.INVOICE_QUANTITY, identifierString, ItemFields.OPEN_QUANTITY);
122                    }
123                }
124            }
125            if (ObjectUtils.isNotNull(item.getExtendedPrice()) && item.getExtendedPrice().isPositive() && ObjectUtils.isNotNull(item.getPoOutstandingQuantity()) && item.getPoOutstandingQuantity().isPositive()) {
126    
127                // here we must require the user to enter some value for quantity if they want a credit amount associated
128                if (ObjectUtils.isNull(item.getItemQuantity()) || item.getItemQuantity().isZero()) {
129                    // here we have a user not entering a quantity with an extended amount but the PO has a quantity...require user to
130                    // enter a quantity
131                    valid = false;
132                    GlobalVariables.getMessageMap().putError(PurapConstants.ITEM_TAB_ERROR_PROPERTY, PurapKeyConstants.ERROR_ITEM_QUANTITY_REQUIRED, ItemFields.INVOICE_QUANTITY, identifierString, ItemFields.OPEN_QUANTITY);
133                }
134            }
135    
136            // check that non-quantity based items are not trying to pay on a zero encumbrance amount (check only prior to ap approval)
137            if ((ObjectUtils.isNull(item.getPaymentRequest().getPurapDocumentIdentifier())) || (PurapConstants.PaymentRequestStatuses.IN_PROCESS.equals(item.getPaymentRequest().getStatusCode()))) {
138                if ((item.getItemType().isAmountBasedGeneralLedgerIndicator()) && ((item.getExtendedPrice() != null) && item.getExtendedPrice().isNonZero())) {
139                    if (item.getPoOutstandingAmount() == null || item.getPoOutstandingAmount().isZero()) {
140                        valid = false;
141                        GlobalVariables.getMessageMap().putError(PurapConstants.ITEM_TAB_ERROR_PROPERTY, PurapKeyConstants.ERROR_ITEM_AMOUNT_ALREADY_PAID, identifierString);
142                    }
143                }
144            }
145    
146            return valid;
147        }
148    
149        /**
150         * Validates that the item must contain at least one account
151         * 
152         * @param item - payment request item
153         * @return
154         */
155        public boolean validateItemWithoutAccounts(PaymentRequestItem item, String identifierString) {
156            boolean valid = true;
157            if (ObjectUtils.isNotNull(item.getItemUnitPrice()) && (new KualiDecimal(item.getItemUnitPrice())).isNonZero() && item.isAccountListEmpty()) {
158                valid = false;
159                GlobalVariables.getMessageMap().putError(PurapConstants.ITEM_TAB_ERROR_PROPERTY, PurapKeyConstants.ERROR_ITEM_ACCOUNTING_INCOMPLETE, identifierString);
160            }
161            return valid;
162        }
163    
164        /**
165         * Validates the totals for the item by account, that the total by each accounting line for the item, matches
166         * the extended price on the item.
167         * 
168         * @param paymentRequestDocument - payment request document
169         * @param item - payment request item to validate
170         * @param identifierString - identifier string used to mark in an error map
171         * @return
172         */
173        public boolean validateItemAccounts(PaymentRequestDocument paymentRequestDocument, PaymentRequestItem item, String identifierString) {
174            boolean valid = true;
175            List<PurApAccountingLine> accountingLines = item.getSourceAccountingLines();
176            KualiDecimal itemTotal = item.getTotalAmount();
177            KualiDecimal accountTotal = KualiDecimal.ZERO;
178            for (PurApAccountingLine accountingLine : accountingLines) {                        
179                valid &= reviewAccountingLineValidation(paymentRequestDocument, accountingLine);            
180                accountTotal = accountTotal.add(accountingLine.getAmount());
181            }
182            if (purapService.isFullDocumentEntryCompleted((PaymentRequestDocument) paymentRequestDocument)) {
183                // check amounts not percent after full entry
184                if (accountTotal.compareTo(itemTotal) != 0) {
185                    valid = false;
186                    GlobalVariables.getMessageMap().putError(PurapConstants.ITEM_TAB_ERROR_PROPERTY, PurapKeyConstants.ERROR_ITEM_ACCOUNTING_AMOUNT_TOTAL, identifierString);
187                }
188            }
189            return valid;
190        }
191    
192        public CompositeValidation getReviewAccountingLineValidation() {
193            return reviewAccountingLineValidation;
194        }
195    
196        public void setReviewAccountingLineValidation(CompositeValidation reviewAccountingLineValidation) {
197            this.reviewAccountingLineValidation = reviewAccountingLineValidation;
198        }
199    
200        public PurapService getPurapService() {
201            return purapService;
202        }
203    
204        public void setPurapService(PurapService purapService) {
205            this.purapService = purapService;
206        }
207    
208        public PurApItem getItemForValidation() {
209            return itemForValidation;
210        }
211    
212        public void setItemForValidation(PurApItem itemForValidation) {
213            this.itemForValidation = itemForValidation;
214        }
215    
216        protected boolean reviewAccountingLineValidation(PaymentRequestDocument document, PurApAccountingLine accountingLine){
217            boolean valid = true;
218            List<Validation> gauntlet = new ArrayList<Validation>();
219            this.preqDocument = document;
220            this.preqAccountingLine = accountingLine;
221            
222            createGauntlet(reviewAccountingLineValidation);
223            
224            for (Validation validation : gauntlet) {
225                valid &= validation.validate(event);
226            }
227    
228            return valid;
229        }
230        
231        protected void createGauntlet(CompositeValidation validation) {
232            for (Validation val : validation.getValidations()) {
233                if (val instanceof CompositeValidation) {
234                    createGauntlet((CompositeValidation)val);
235                } else if (val instanceof BusinessObjectDataDictionaryValidation) {
236                    addParametersToValidation((BusinessObjectDataDictionaryValidation)val);
237                } else if (val instanceof AccountingLineAmountPositiveValidation) {
238                    addParametersToValidation((AccountingLineAmountPositiveValidation)val);
239                } else if (val instanceof AccountingLineDataDictionaryValidation) {
240                    addParametersToValidation((AccountingLineDataDictionaryValidation)val);
241                } else if (val instanceof AccountingLineValuesAllowedValidationHutch) {
242                    addParametersToValidation((AccountingLineValuesAllowedValidationHutch)val);
243                } else {
244                    throw new IllegalStateException("Validations in the PaymentRequestProcessItemValidation must contain specific instances of validation");
245                }
246            }
247        }
248    
249        protected void addParametersToValidation(BusinessObjectDataDictionaryValidation validation) {
250            validation.setBusinessObjectForValidation(this.preqAccountingLine);
251        }
252    
253        protected void addParametersToValidation(AccountingLineAmountPositiveValidation validation) {
254            validation.setAccountingDocumentForValidation(this.preqDocument);
255            validation.setAccountingLineForValidation(this.preqAccountingLine);
256        }
257    
258        protected void addParametersToValidation(AccountingLineDataDictionaryValidation validation) {
259            validation.setAccountingLineForValidation(this.preqAccountingLine);
260        }
261    
262        protected void addParametersToValidation(AccountingLineValuesAllowedValidationHutch validation) {        
263            validation.setAccountingDocumentForValidation(this.preqDocument);
264            validation.setAccountingLineForValidation(this.preqAccountingLine);
265        }
266    
267        /**
268         * Gets the event attribute. 
269         * @return Returns the event.
270         */
271        protected AttributedDocumentEvent getEvent() {
272            return event;
273        }
274    
275        /**
276         * Sets the event attribute value.
277         * @param event The event to set.
278         */
279        protected void setEvent(AttributedDocumentEvent event) {
280            this.event = event;
281        }
282    
283        /**
284         * Gets the preqDocument attribute. 
285         * @return Returns the preqDocument.
286         */
287        protected PaymentRequestDocument getPreqDocument() {
288            return preqDocument;
289        }
290    
291        /**
292         * Sets the preqDocument attribute value.
293         * @param preqDocument The preqDocument to set.
294         */
295        protected void setPreqDocument(PaymentRequestDocument preqDocument) {
296            this.preqDocument = preqDocument;
297        }
298    
299        /**
300         * Gets the preqAccountingLine attribute. 
301         * @return Returns the preqAccountingLine.
302         */
303        protected PurApAccountingLine getPreqAccountingLine() {
304            return preqAccountingLine;
305        }
306    
307        /**
308         * Sets the preqAccountingLine attribute value.
309         * @param preqAccountingLine The preqAccountingLine to set.
310         */
311        protected void setPreqAccountingLine(PurApAccountingLine preqAccountingLine) {
312            this.preqAccountingLine = preqAccountingLine;
313        }
314    
315    }