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