View Javadoc
1   /*
2    * Copyright 2009 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.ole.module.purap.document.validation.impl;
17  
18  import org.kuali.ole.module.purap.PurapConstants;
19  import org.kuali.ole.module.purap.PurapConstants.ItemFields;
20  import org.kuali.ole.module.purap.PurapKeyConstants;
21  import org.kuali.ole.module.purap.PurapParameterConstants;
22  import org.kuali.ole.module.purap.businessobject.PaymentRequestItem;
23  import org.kuali.ole.module.purap.businessobject.PurApAccountingLine;
24  import org.kuali.ole.module.purap.businessobject.PurApItem;
25  import org.kuali.ole.module.purap.document.PaymentRequestDocument;
26  import org.kuali.ole.module.purap.document.service.PurapService;
27  import org.kuali.ole.select.businessobject.OlePaymentRequestItem;
28  import org.kuali.ole.sys.context.SpringContext;
29  import org.kuali.ole.sys.document.validation.GenericValidation;
30  import org.kuali.ole.sys.document.validation.Validation;
31  import org.kuali.ole.sys.document.validation.event.AttributedDocumentEvent;
32  import org.kuali.ole.sys.document.validation.impl.*;
33  import org.kuali.ole.sys.service.impl.OleParameterConstants;
34  import org.kuali.rice.core.api.util.type.KualiDecimal;
35  import org.kuali.rice.coreservice.framework.parameter.ParameterService;
36  import org.kuali.rice.krad.util.GlobalVariables;
37  import org.kuali.rice.krad.util.ObjectUtils;
38  
39  import java.util.ArrayList;
40  import java.util.List;
41  
42  public class PaymentRequestProcessItemValidation extends GenericValidation {
43  
44      private PurapService purapService;
45      private PurApItem itemForValidation;
46      private AttributedDocumentEvent event;
47      private CompositeValidation reviewAccountingLineValidation;
48      private PaymentRequestDocument preqDocument;
49      private PurApAccountingLine preqAccountingLine;
50  
51      @Override
52      public boolean validate(AttributedDocumentEvent event) {
53          boolean valid = true;
54          this.event = event;
55  
56          PaymentRequestDocument paymentRequestDocument = (PaymentRequestDocument) event.getDocument();
57          PaymentRequestItem preqItem = (PaymentRequestItem) itemForValidation;
58  
59          valid &= validateEachItem(paymentRequestDocument, preqItem);
60  
61          return valid;
62  
63      }
64  
65      /**
66       * Calls another validate item method and passes an identifier string from the item.
67       *
68       * @param paymentRequestDocument - payment request document.
69       * @param item
70       * @return
71       */
72      protected boolean validateEachItem(PaymentRequestDocument paymentRequestDocument, PaymentRequestItem item) {
73          boolean valid = true;
74          String identifierString = item.getItemIdentifierString();
75          valid &= validateItem(paymentRequestDocument, item, identifierString);
76          return valid;
77      }
78  
79      /**
80       * Performs validation if full document entry not completed and peforms varying item validation.
81       * Such as, above the line, items without accounts, items with accounts.
82       *
83       * @param paymentRequestDocument - payment request document
84       * @param item                   - payment request item
85       * @param identifierString       - identifier string used to mark in an error map
86       * @return
87       */
88      public boolean validateItem(PaymentRequestDocument paymentRequestDocument, PaymentRequestItem item, String identifierString) {
89          boolean valid = true;
90          // only run item validations if before full entry
91          if (!purapService.isFullDocumentEntryCompleted(paymentRequestDocument)) {
92              if (item.getItemType().isLineItemIndicator()) {
93                  valid &= validateAboveTheLineItems(item, identifierString, paymentRequestDocument.isReceivingDocumentRequiredIndicator());
94              }
95              valid &= validateItemWithoutAccounts(item, identifierString);
96          }
97          // always run account validations
98          valid &= validateItemAccounts(paymentRequestDocument, item, identifierString);
99          return valid;
100     }
101 
102     /**
103      * Validates above the line items.
104      *
105      * @param item             - payment request item
106      * @param identifierString - identifier string used to mark in an error map
107      * @return
108      */
109     protected boolean validateAboveTheLineItems(PaymentRequestItem item, String identifierString, boolean isReceivingDocumentRequiredIndicator) {
110         boolean valid = true;
111         // Currently Quantity is allowed to be NULL on screen;
112         // must be either a positive number or NULL for DB
113         if (ObjectUtils.isNotNull(item.getItemQuantity())) {
114             if (item.getItemQuantity().isNegative()) {
115                 // if quantity is negative give an error
116                 valid = false;
117                 GlobalVariables.getMessageMap().putError(PurapConstants.ITEM_TAB_ERROR_PROPERTY, PurapKeyConstants.ERROR_ITEM_AMOUNT_BELOW_ZERO, ItemFields.INVOICE_QUANTITY, identifierString);
118             }
119             if (!isReceivingDocumentRequiredIndicator) {
120                 if (item.getPoOutstandingQuantity().isLessThan(item.getItemQuantity())) {
121                     valid = false;
122                     GlobalVariables.getMessageMap().putError(PurapConstants.ITEM_TAB_ERROR_PROPERTY, PurapKeyConstants.ERROR_ITEM_QUANTITY_TOO_MANY, ItemFields.INVOICE_QUANTITY, identifierString, ItemFields.OPEN_QUANTITY);
123                 }
124             }
125         }
126         if (ObjectUtils.isNotNull(item.getExtendedPrice()) && item.getExtendedPrice().isPositive() && ObjectUtils.isNotNull(item.getPoOutstandingQuantity()) && item.getPoOutstandingQuantity().isPositive()) {
127 
128             // here we must require the user to enter some value for quantity if they want a credit amount associated
129             if (ObjectUtils.isNull(item.getItemQuantity()) || item.getItemQuantity().isZero()) {
130                 // here we have a user not entering a quantity with an extended amount but the PO has a quantity...require user to
131                 // enter a quantity
132                 valid = false;
133                 GlobalVariables.getMessageMap().putError(PurapConstants.ITEM_TAB_ERROR_PROPERTY, PurapKeyConstants.ERROR_ITEM_QUANTITY_REQUIRED, ItemFields.INVOICE_QUANTITY, identifierString, ItemFields.OPEN_QUANTITY);
134             }
135         }
136 
137         // check that non-quantity based items are not trying to pay on a zero encumbrance amount (check only prior to ap approval)
138         if ((ObjectUtils.isNull(item.getPaymentRequest().getPurapDocumentIdentifier())) || (PurapConstants.PaymentRequestStatuses.APPDOC_IN_PROCESS.equals(item.getPaymentRequest().getApplicationDocumentStatus()))) {
139 // RICE20 : needed? :  !purapService.isFullDocumentEntryCompleted(item.getPaymentRequest())) {
140             if ((item.getItemType().isAmountBasedGeneralLedgerIndicator()) && ((item.getExtendedPrice() != null) && item.getExtendedPrice().isNonZero())) {
141                 if (item.getPoOutstandingAmount() == null || item.getPoOutstandingAmount().isZero()) {
142                     valid = false;
143                     GlobalVariables.getMessageMap().putError(PurapConstants.ITEM_TAB_ERROR_PROPERTY, PurapKeyConstants.ERROR_ITEM_AMOUNT_ALREADY_PAID, identifierString);
144                 }
145             }
146         }
147 
148         return valid;
149     }
150 
151     /**
152      * Validates that the item must contain at least one account
153      *
154      * @param item - payment request item
155      * @return
156      */
157     public boolean validateItemWithoutAccounts(PaymentRequestItem item, String identifierString) {
158         boolean valid = true;
159         if (ObjectUtils.isNotNull(item.getItemUnitPrice()) && (new KualiDecimal(item.getItemUnitPrice())).isNonZero() && item.isAccountListEmpty()) {
160             valid = false;
161             GlobalVariables.getMessageMap().putError(PurapConstants.ITEM_TAB_ERROR_PROPERTY, PurapKeyConstants.ERROR_ITEM_ACCOUNTING_INCOMPLETE, identifierString);
162         }
163         return valid;
164     }
165 
166     /**
167      * Validates the totals for the item by account, that the total by each accounting line for the item, matches
168      * the extended price on the item.
169      *
170      * @param paymentRequestDocument - payment request document
171      * @param item                   - payment request item to validate
172      * @param identifierString       - identifier string used to mark in an error map
173      * @return
174      */
175     public boolean validateItemAccounts(PaymentRequestDocument paymentRequestDocument, PaymentRequestItem item, String identifierString) {
176         boolean valid = true;
177         List<PurApAccountingLine> accountingLines = item.getSourceAccountingLines();
178         KualiDecimal itemTotal = item.getTotalAmount();
179         KualiDecimal accountTotal = KualiDecimal.ZERO;
180         //KualiDecimal prorateSurcharge = KualiDecimal.ZERO;
181         OlePaymentRequestItem preqItem = (OlePaymentRequestItem) item;
182         if (preqItem.getItemType().isQuantityBasedGeneralLedgerIndicator() && preqItem.getExtendedPrice() != null && preqItem.getExtendedPrice().compareTo(KualiDecimal.ZERO) != 0) {
183             /*if (preqItem.getItemSurcharge() != null && preqItem.getItemTypeCode().equals("ITEM")) {
184                 prorateSurcharge = new KualiDecimal(preqItem.getItemSurcharge()).multiply(preqItem.getItemQuantity());
185             }
186             itemTotal = itemTotal.subtract(prorateSurcharge);*/
187         }
188         for (PurApAccountingLine accountingLine : accountingLines) {
189             if (accountingLine.getAmount().isZero()) {
190                 if (!canApproveAccountingLinesWithZeroAmount()) {
191                     GlobalVariables.getMessageMap().putError(PurapConstants.ITEM_TAB_ERROR_PROPERTY, PurapKeyConstants.ERROR_ITEM_ACCOUNTING_AMOUNT_INVALID, itemForValidation.getItemIdentifierString());
192                     valid &= false;
193                 }
194             }
195             valid &= reviewAccountingLineValidation(paymentRequestDocument, accountingLine);
196             accountTotal = accountTotal.add(accountingLine.getAmount());
197         }
198         if (purapService.isFullDocumentEntryCompleted(paymentRequestDocument)) {
199             // check amounts not percent after full entry
200             if (accountTotal.compareTo(itemTotal) != 0) {
201                 valid = false;
202                 GlobalVariables.getMessageMap().putError(PurapConstants.ITEM_TAB_ERROR_PROPERTY, PurapKeyConstants.ERROR_ITEM_ACCOUNTING_AMOUNT_TOTAL, identifierString);
203             }
204         }
205         return  valid;
206     }
207 
208     public CompositeValidation getReviewAccountingLineValidation() {
209         return reviewAccountingLineValidation;
210     }
211 
212     public void setReviewAccountingLineValidation(CompositeValidation reviewAccountingLineValidation) {
213         this.reviewAccountingLineValidation = reviewAccountingLineValidation;
214     }
215 
216     public PurapService getPurapService() {
217         return purapService;
218     }
219 
220     public void setPurapService(PurapService purapService) {
221         this.purapService = purapService;
222     }
223 
224     public PurApItem getItemForValidation() {
225         return itemForValidation;
226     }
227 
228     public void setItemForValidation(PurApItem itemForValidation) {
229         this.itemForValidation = itemForValidation;
230     }
231 
232     protected boolean reviewAccountingLineValidation(PaymentRequestDocument document, PurApAccountingLine accountingLine) {
233         boolean valid = true;
234         List<Validation> gauntlet = new ArrayList<Validation>();
235         this.preqDocument = document;
236         this.preqAccountingLine = accountingLine;
237 
238         createGauntlet(reviewAccountingLineValidation);
239 
240         for (Validation validation : gauntlet) {
241             valid &= validation.validate(event);
242         }
243 
244         return valid;
245     }
246 
247     protected void createGauntlet(CompositeValidation validation) {
248         for (Validation val : validation.getValidations()) {
249             if (val instanceof CompositeValidation) {
250                 createGauntlet((CompositeValidation) val);
251             } else if (val instanceof BusinessObjectDataDictionaryValidation) {
252                 addParametersToValidation((BusinessObjectDataDictionaryValidation) val);
253             } else if (val instanceof AccountingLineAmountPositiveValidation) {
254                 addParametersToValidation((AccountingLineAmountPositiveValidation) val);
255             } else if (val instanceof AccountingLineDataDictionaryValidation) {
256                 addParametersToValidation((AccountingLineDataDictionaryValidation) val);
257             } else if (val instanceof AccountingLineValuesAllowedValidationHutch) {
258                 addParametersToValidation((AccountingLineValuesAllowedValidationHutch) val);
259             } else {
260                 throw new IllegalStateException("Validations in the PaymentRequestProcessItemValidation must contain specific instances of validation");
261             }
262         }
263     }
264 
265     /**
266      * checks if an accounting line with zero dollar amount can be approved.  This will check
267      * the system parameter APPROVE_ACCOUNTING_LINES_WITH_ZERO_DOLLAR_AMOUNT_IND and determines if the
268      * line can be approved or not.
269      *
270      * @return true if the system parameter value is Y else returns N.
271      */
272     public boolean canApproveAccountingLinesWithZeroAmount() {
273         boolean canApproveLine = false;
274 
275         // get parameter to see if accounting line with zero dollar amount can be approved.
276         String approveZeroAmountLine = SpringContext.getBean(ParameterService.class).getParameterValueAsString(OleParameterConstants.PURCHASING_DOCUMENT.class, PurapParameterConstants.APPROVE_ACCOUNTING_LINES_WITH_ZERO_DOLLAR_AMOUNT_IND);
277 
278         if ("Y".equalsIgnoreCase(approveZeroAmountLine)) {
279             return true;
280         }
281 
282         return canApproveLine;
283     }
284 
285     protected void addParametersToValidation(BusinessObjectDataDictionaryValidation validation) {
286         validation.setBusinessObjectForValidation(this.preqAccountingLine);
287     }
288 
289     protected void addParametersToValidation(AccountingLineAmountPositiveValidation validation) {
290         validation.setAccountingDocumentForValidation(this.preqDocument);
291         validation.setAccountingLineForValidation(this.preqAccountingLine);
292     }
293 
294     protected void addParametersToValidation(AccountingLineDataDictionaryValidation validation) {
295         validation.setAccountingLineForValidation(this.preqAccountingLine);
296     }
297 
298     protected void addParametersToValidation(AccountingLineValuesAllowedValidationHutch validation) {
299         validation.setAccountingDocumentForValidation(this.preqDocument);
300         validation.setAccountingLineForValidation(this.preqAccountingLine);
301     }
302 
303     /**
304      * Gets the event attribute.
305      *
306      * @return Returns the event.
307      */
308     protected AttributedDocumentEvent getEvent() {
309         return event;
310     }
311 
312     /**
313      * Sets the event attribute value.
314      *
315      * @param event The event to set.
316      */
317     protected void setEvent(AttributedDocumentEvent event) {
318         this.event = event;
319     }
320 
321     /**
322      * Gets the preqDocument attribute.
323      *
324      * @return Returns the preqDocument.
325      */
326     protected PaymentRequestDocument getPreqDocument() {
327         return preqDocument;
328     }
329 
330     /**
331      * Sets the preqDocument attribute value.
332      *
333      * @param preqDocument The preqDocument to set.
334      */
335     protected void setPreqDocument(PaymentRequestDocument preqDocument) {
336         this.preqDocument = preqDocument;
337     }
338 
339     /**
340      * Gets the preqAccountingLine attribute.
341      *
342      * @return Returns the preqAccountingLine.
343      */
344     protected PurApAccountingLine getPreqAccountingLine() {
345         return preqAccountingLine;
346     }
347 
348     /**
349      * Sets the preqAccountingLine attribute value.
350      *
351      * @param preqAccountingLine The preqAccountingLine to set.
352      */
353     protected void setPreqAccountingLine(PurApAccountingLine preqAccountingLine) {
354         this.preqAccountingLine = preqAccountingLine;
355     }
356 
357 }