001/*
002 * Copyright 2009 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 */
016package org.kuali.ole.module.purap.document.validation.impl;
017
018import org.kuali.ole.module.purap.PurapConstants;
019import org.kuali.ole.module.purap.PurapConstants.ItemFields;
020import org.kuali.ole.module.purap.PurapKeyConstants;
021import org.kuali.ole.module.purap.PurapParameterConstants;
022import org.kuali.ole.module.purap.businessobject.PaymentRequestItem;
023import org.kuali.ole.module.purap.businessobject.PurApAccountingLine;
024import org.kuali.ole.module.purap.businessobject.PurApItem;
025import org.kuali.ole.module.purap.document.PaymentRequestDocument;
026import org.kuali.ole.module.purap.document.service.PurapService;
027import org.kuali.ole.select.businessobject.OlePaymentRequestItem;
028import org.kuali.ole.sys.context.SpringContext;
029import org.kuali.ole.sys.document.validation.GenericValidation;
030import org.kuali.ole.sys.document.validation.Validation;
031import org.kuali.ole.sys.document.validation.event.AttributedDocumentEvent;
032import org.kuali.ole.sys.document.validation.impl.*;
033import org.kuali.ole.sys.service.impl.OleParameterConstants;
034import org.kuali.rice.core.api.util.type.KualiDecimal;
035import org.kuali.rice.coreservice.framework.parameter.ParameterService;
036import org.kuali.rice.krad.util.GlobalVariables;
037import org.kuali.rice.krad.util.ObjectUtils;
038
039import java.util.ArrayList;
040import java.util.List;
041
042public 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    @Override
052    public boolean validate(AttributedDocumentEvent event) {
053        boolean valid = true;
054        this.event = event;
055
056        PaymentRequestDocument paymentRequestDocument = (PaymentRequestDocument) event.getDocument();
057        PaymentRequestItem preqItem = (PaymentRequestItem) itemForValidation;
058
059        valid &= validateEachItem(paymentRequestDocument, preqItem);
060
061        return valid;
062
063    }
064
065    /**
066     * Calls another validate item method and passes an identifier string from the item.
067     *
068     * @param paymentRequestDocument - payment request document.
069     * @param item
070     * @return
071     */
072    protected boolean validateEachItem(PaymentRequestDocument paymentRequestDocument, PaymentRequestItem item) {
073        boolean valid = true;
074        String identifierString = item.getItemIdentifierString();
075        valid &= validateItem(paymentRequestDocument, item, identifierString);
076        return valid;
077    }
078
079    /**
080     * Performs validation if full document entry not completed and peforms varying item validation.
081     * Such as, above the line, items without accounts, items with accounts.
082     *
083     * @param paymentRequestDocument - payment request document
084     * @param item                   - payment request item
085     * @param identifierString       - identifier string used to mark in an error map
086     * @return
087     */
088    public boolean validateItem(PaymentRequestDocument paymentRequestDocument, PaymentRequestItem item, String identifierString) {
089        boolean valid = true;
090        // only run item validations if before full entry
091        if (!purapService.isFullDocumentEntryCompleted(paymentRequestDocument)) {
092            if (item.getItemType().isLineItemIndicator()) {
093                valid &= validateAboveTheLineItems(item, identifierString, paymentRequestDocument.isReceivingDocumentRequiredIndicator());
094            }
095            valid &= validateItemWithoutAccounts(item, identifierString);
096        }
097        // always run account validations
098        valid &= validateItemAccounts(paymentRequestDocument, item, identifierString);
099        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}