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 }