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