001: /*
002: * Copyright 2006-2007 The Kuali Foundation.
003: *
004: * Licensed under the Educational Community License, Version 1.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/ecl1.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.module.purap.rules;
017:
018: import static org.kuali.kfs.KFSConstants.GL_DEBIT_CODE;
019:
020: import java.util.List;
021:
022: import org.apache.commons.lang.StringUtils;
023: import org.kuali.RicePropertyConstants;
024: import org.kuali.core.datadictionary.validation.fieldlevel.ZipcodeValidationPattern;
025: import org.kuali.core.document.AmountTotaling;
026: import org.kuali.core.util.ErrorMap;
027: import org.kuali.core.util.GlobalVariables;
028: import org.kuali.core.util.KualiDecimal;
029: import org.kuali.core.util.ObjectUtils;
030: import org.kuali.core.workflow.service.KualiWorkflowDocument;
031: import org.kuali.kfs.KFSConstants;
032: import org.kuali.kfs.KFSKeyConstants;
033: import org.kuali.kfs.bo.AccountingLine;
034: import org.kuali.kfs.bo.GeneralLedgerPendingEntry;
035: import org.kuali.kfs.context.SpringContext;
036: import org.kuali.kfs.document.AccountingDocument;
037: import org.kuali.module.purap.PurapConstants;
038: import org.kuali.module.purap.PurapKeyConstants;
039: import org.kuali.module.purap.PurapPropertyConstants;
040: import org.kuali.module.purap.PurapConstants.ItemTypeCodes;
041: import org.kuali.module.purap.PurapConstants.PurapDocTypeCodes;
042: import org.kuali.module.purap.PurapWorkflowConstants.PurchaseOrderDocument.NodeDetailEnum;
043: import org.kuali.module.purap.bo.PurApItem;
044: import org.kuali.module.purap.bo.PurchaseOrderItem;
045: import org.kuali.module.purap.bo.PurchaseOrderVendorStipulation;
046: import org.kuali.module.purap.document.PurchaseOrderDocument;
047: import org.kuali.module.purap.document.PurchasingAccountsPayableDocument;
048: import org.kuali.module.purap.document.PurchasingDocument;
049: import org.kuali.module.purap.service.PurapGeneralLedgerService;
050: import org.kuali.module.vendor.VendorPropertyConstants;
051: import org.kuali.module.vendor.service.PhoneNumberService;
052:
053: /**
054: * Business rule(s) applicable to Purchase Order document.
055: */
056: public class PurchaseOrderDocumentRule extends
057: PurchasingDocumentRuleBase {
058:
059: /**
060: * Overrides the method in PurchasingDocumentRuleBase class in order to add validation for the Vendor Stipulation Tab. Tab
061: * included on Purchase Order Documents is Vendor Stipulation.
062: *
063: * @param purapDocument the purchase order document to be validated
064: * @return boolean false when an error is found in any validation.
065: * @see org.kuali.module.purap.rules.PurchasingAccountsPayableDocumentRuleBase#processValidation(org.kuali.module.purap.document.PurchasingAccountsPayableDocument)
066: */
067: @Override
068: public boolean processValidation(
069: PurchasingAccountsPayableDocument purapDocument) {
070: boolean valid = super .processValidation(purapDocument);
071: valid &= processAdditionalValidation((PurchasingDocument) purapDocument);
072: valid &= processVendorStipulationValidation((PurchaseOrderDocument) purapDocument);
073:
074: return valid;
075: }
076:
077: /**
078: * Performs any validation for the Additional tab, but currently it only returns true. Someday we might be able to just remove
079: * this.
080: *
081: * @param purDocument the purchase order document to be validated
082: * @return boolean true (always return true for now)
083: */
084: public boolean processAdditionalValidation(
085: PurchasingDocument purDocument) {
086: boolean valid = true;
087:
088: return valid;
089: }
090:
091: /**
092: * Overrides the method in PurchasingDocumentRuleBase in order to call validateEmptyItemWithAccounts, validateItemForAmendment
093: * and validateTradeInAndDiscountCoexistence in addition to what the superclass method has already provided.
094: *
095: * @param purapDocument the purchase order document to be validated
096: * @return boolean false when an error is found in any validation.
097: * @see org.kuali.module.purap.rules.PurchasingDocumentRuleBase#processItemValidation(org.kuali.module.purap.document.PurchasingDocument)
098: */
099: @Override
100: public boolean processItemValidation(
101: PurchasingAccountsPayableDocument purapDocument) {
102: boolean valid = super .processItemValidation(purapDocument);
103: for (PurApItem item : purapDocument.getItems()) {
104: String identifierString = (item.getItemType()
105: .isItemTypeAboveTheLineIndicator() ? "Item "
106: + item.getItemLineNumber().toString() : item
107: .getItemType().getItemTypeDescription());
108: valid &= validateEmptyItemWithAccounts(
109: (PurchaseOrderItem) item, identifierString);
110: if (purapDocument.getDocumentHeader().getWorkflowDocument() != null
111: && purapDocument
112: .getDocumentHeader()
113: .getWorkflowDocument()
114: .getDocumentType()
115: .equals(
116: PurapConstants.PurchaseOrderDocTypes.PURCHASE_ORDER_AMENDMENT_DOCUMENT)) {
117: valid &= validateItemForAmendment(
118: (PurchaseOrderItem) item, identifierString);
119: }
120: }
121: valid &= validateTradeInAndDiscountCoexistence((PurchasingDocument) purapDocument);
122:
123: return valid;
124: }
125:
126: /**
127: * Validates items for amendment.
128: *
129: * @param item the item to be validated
130: * @param identifierString the identifier string of the item to be validated
131: * @return boolean true if it passes the validation and false otherwise.
132: */
133: private boolean validateItemForAmendment(PurchaseOrderItem item,
134: String identifierString) {
135: boolean valid = true;
136: if ((item.getItemInvoicedTotalQuantity() != null)
137: && (!(item.getItemInvoicedTotalQuantity()).isZero())) {
138: if (item.getItemQuantity() == null) {
139: valid = false;
140: GlobalVariables.getErrorMap().putError(
141: PurapConstants.ITEM_TAB_ERROR_PROPERTY,
142: PurapKeyConstants.ERROR_ITEM_AMND_NULL,
143: "Item Quantity", identifierString);
144: } else if (item.getItemQuantity().compareTo(
145: item.getItemInvoicedTotalQuantity()) < 0) {
146: valid = false;
147: GlobalVariables.getErrorMap().putError(
148: PurapConstants.ITEM_TAB_ERROR_PROPERTY,
149: PurapKeyConstants.ERROR_ITEM_AMND_INVALID,
150: "Item Quantity", identifierString);
151: }
152: }
153:
154: if (item.getItemInvoicedTotalAmount() != null) {
155: KualiDecimal total = item.getExtendedPrice();
156: if ((total == null)
157: || total.compareTo(item
158: .getItemInvoicedTotalAmount()) < 0) {
159: valid = false;
160: GlobalVariables.getErrorMap().putError(
161: PurapConstants.ITEM_TAB_ERROR_PROPERTY,
162: PurapKeyConstants.ERROR_ITEM_AMND_INVALID_AMT,
163: "Item Extended Price", identifierString);
164: }
165: }
166:
167: return valid;
168: }
169:
170: /**
171: * Validates that the item detail must not be empty if its account is not empty and its item type is ITEM.
172: *
173: * @param item the item to be validated
174: * @param identifierString the identifier string of the item to be validated
175: * @return boolean false if it is an above the line item and the item detail is empty and the account list is not empty.
176: */
177: boolean validateEmptyItemWithAccounts(PurchaseOrderItem item,
178: String identifierString) {
179: boolean valid = true;
180: if (item.getItemType().isItemTypeAboveTheLineIndicator()
181: && item.isItemDetailEmpty()
182: && !item.isAccountListEmpty()) {
183: valid = false;
184: GlobalVariables
185: .getErrorMap()
186: .putError(
187: PurapConstants.ITEM_TAB_ERROR_PROPERTY,
188: PurapKeyConstants.ERROR_ITEM_ACCOUNTING_NOT_ALLOWED,
189: identifierString);
190: }
191:
192: return valid;
193: }
194:
195: /**
196: * Validates that the purchase order cannot have both trade in and discount item.
197: *
198: * @param purDocument the purchase order document to be validated
199: * @return boolean false if trade in and discount both exist.
200: */
201: boolean validateTradeInAndDiscountCoexistence(
202: PurchasingDocument purDocument) {
203: boolean discountExists = false;
204: boolean tradeInExists = false;
205:
206: for (PurApItem item : purDocument.getItems()) {
207: if (item.getItemTypeCode().equals(
208: ItemTypeCodes.ITEM_TYPE_ORDER_DISCOUNT_CODE)) {
209: discountExists = true;
210: if (tradeInExists) {
211: GlobalVariables
212: .getErrorMap()
213: .putError(
214: PurapConstants.ITEM_TAB_ERROR_PROPERTY,
215: PurapKeyConstants.ERROR_ITEM_TRADEIN_DISCOUNT_COEXISTENCE);
216:
217: return false;
218: }
219: } else if (item.getItemTypeCode().equals(
220: ItemTypeCodes.ITEM_TYPE_TRADE_IN_CODE)) {
221: tradeInExists = true;
222: if (discountExists) {
223: GlobalVariables
224: .getErrorMap()
225: .putError(
226: PurapConstants.ITEM_TAB_ERROR_PROPERTY,
227: PurapKeyConstants.ERROR_ITEM_TRADEIN_DISCOUNT_COEXISTENCE);
228:
229: return false;
230: }
231: }
232: }
233:
234: return true;
235: }
236:
237: /**
238: * Validation for the Stipulation tab.
239: *
240: * @param poDocument the purchase order document to be validated
241: * @return boolean false if the vendor stipulation description is blank.
242: */
243: public boolean processVendorStipulationValidation(
244: PurchaseOrderDocument poDocument) {
245: boolean valid = true;
246: List<PurchaseOrderVendorStipulation> stipulations = poDocument
247: .getPurchaseOrderVendorStipulations();
248: for (int i = 0; i < stipulations.size(); i++) {
249: PurchaseOrderVendorStipulation stipulation = stipulations
250: .get(i);
251: if (StringUtils.isBlank(stipulation
252: .getVendorStipulationDescription())) {
253: GlobalVariables
254: .getErrorMap()
255: .putError(
256: PurapPropertyConstants.VENDOR_STIPULATION
257: + "["
258: + i
259: + "]."
260: + PurapPropertyConstants.VENDOR_STIPULATION_DESCRIPTION,
261: PurapKeyConstants.ERROR_STIPULATION_DESCRIPTION);
262: valid = false;
263: }
264: }
265:
266: return valid;
267: }
268:
269: /**
270: * Overrides the method in PurchasingDocumentRuleBase in order to add validations that are specific for Purchase Orders that
271: * aren't required for Requisitions.
272: *
273: * @param purapDocument the purchase order document to be validated
274: * @return boolean false when there is a failed validation.
275: * @see org.kuali.module.purap.rules.PurchasingDocumentRuleBase#processVendorValidation(org.kuali.module.purap.document.PurchasingAccountsPayableDocument)
276: */
277: @Override
278: public boolean processVendorValidation(
279: PurchasingAccountsPayableDocument purapDocument) {
280: ErrorMap errorMap = GlobalVariables.getErrorMap();
281: errorMap.clearErrorPath();
282: errorMap.addToErrorPath(RicePropertyConstants.DOCUMENT);
283: boolean valid = super .processVendorValidation(purapDocument);
284: PurchaseOrderDocument poDocument = (PurchaseOrderDocument) purapDocument;
285: // check to see if the vendor exists in the database, i.e. its ID is not null
286: Integer vendorHeaderID = poDocument
287: .getVendorHeaderGeneratedIdentifier();
288: if (ObjectUtils.isNull(vendorHeaderID)) {
289: valid = false;
290: errorMap.putError(VendorPropertyConstants.VENDOR_NAME,
291: PurapKeyConstants.ERROR_NONEXIST_VENDOR);
292: }
293: if (StringUtils.isBlank(poDocument.getVendorCountryCode())) {
294: valid = false;
295: errorMap.putError(
296: PurapPropertyConstants.VENDOR_COUNTRY_CODE,
297: KFSKeyConstants.ERROR_REQUIRED);
298: } else if (poDocument.getVendorCountryCode().equals(
299: KFSConstants.COUNTRY_CODE_UNITED_STATES)) {
300: if (StringUtils.isBlank(poDocument.getVendorStateCode())) {
301: valid = false;
302: errorMap.putError(
303: PurapPropertyConstants.VENDOR_STATE_CODE,
304: KFSKeyConstants.ERROR_REQUIRED_FOR_US);
305: }
306: ZipcodeValidationPattern zipPattern = new ZipcodeValidationPattern();
307: if (StringUtils.isBlank(poDocument.getVendorPostalCode())) {
308: valid = false;
309: errorMap.putError(
310: PurapPropertyConstants.VENDOR_POSTAL_CODE,
311: KFSKeyConstants.ERROR_REQUIRED_FOR_US);
312: } else if (!zipPattern.matches(poDocument
313: .getVendorPostalCode())) {
314: valid = false;
315: errorMap.putError(
316: PurapPropertyConstants.VENDOR_POSTAL_CODE,
317: PurapKeyConstants.ERROR_POSTAL_CODE_INVALID);
318: }
319: }
320: errorMap.clearErrorPath();
321:
322: return valid;
323: }
324:
325: /**
326: * Validate that if Vendor Id (VendorHeaderGeneratedId) is not empty, and tranmission method is fax, vendor fax number cannot be
327: * empty and must be valid.
328: *
329: * @param purDocument the purchase order document to be validated
330: * @return boolean false if VendorHeaderGeneratedId is not empty, tranmission method is fax, and VendorFaxNumber is empty or
331: * invalid.
332: */
333: private boolean validateFaxNumberIfTransmissionTypeIsFax(
334: PurchasingDocument purDocument) {
335: boolean valid = true;
336: GlobalVariables.getErrorMap().clearErrorPath();
337: GlobalVariables.getErrorMap().addToErrorPath(
338: RicePropertyConstants.DOCUMENT);
339: if (ObjectUtils.isNotNull(purDocument
340: .getVendorHeaderGeneratedIdentifier())
341: && purDocument
342: .getPurchaseOrderTransmissionMethodCode()
343: .equals(
344: PurapConstants.POTransmissionMethods.FAX)) {
345: if (ObjectUtils.isNull(purDocument.getVendorFaxNumber())
346: || !SpringContext.getBean(PhoneNumberService.class)
347: .isValidPhoneNumber(
348: purDocument.getVendorFaxNumber())) {
349: GlobalVariables
350: .getErrorMap()
351: .putError(
352: PurapPropertyConstants.VENDOR_FAX_NUMBER,
353: PurapKeyConstants.ERROR_FAX_NUMBER_PO_TRANSMISSION_TYPE);
354: valid &= false;
355: }
356: }
357: GlobalVariables.getErrorMap().clearErrorPath();
358:
359: return valid;
360: }
361:
362: /**
363: * Validate that if the PurchaseOrderTotalLimit is not null then the TotalDollarAmount cannot be greater than the
364: * PurchaseOrderTotalLimit.
365: *
366: * @param purDocument the purchase order document to be validated
367: * @return True if the TotalDollarAmount is less than the PurchaseOrderTotalLimit. False otherwise.
368: */
369: public boolean validateTotalDollarAmountIsLessThanPurchaseOrderTotalLimit(
370: PurchasingDocument purDocument) {
371: boolean valid = true;
372: KualiDecimal totalAmount = ((AmountTotaling) purDocument)
373: .getTotalDollarAmount();
374: if (ObjectUtils.isNotNull(purDocument
375: .getPurchaseOrderTotalLimit())
376: && ObjectUtils.isNotNull(totalAmount)) {
377: if (totalAmount.isGreaterThan(purDocument
378: .getPurchaseOrderTotalLimit())) {
379: valid &= false;
380: GlobalVariables
381: .getMessageList()
382: .add(
383: PurapKeyConstants.PO_TOTAL_GREATER_THAN_PO_TOTAL_LIMIT);
384: }
385: }
386:
387: return valid;
388: }
389:
390: /**
391: * Overrides the method in PurapAccountingDocumentRuleBase in order to check that if the document will stop in Internal
392: * Purchasing Review node, then return true.
393: *
394: * @param financialDocument the purchase order document to be validated
395: * @param accountingLine the accounting line to be validated
396: * @param action the AccountingLineAction enum that indicates what is being done to an accounting line
397: * @return boolean true if the document will stop in Internal Purchasing Review node, otherwise return the result of the
398: * checkAccountingLineAccountAccessibility in PurapAccountingDocumentRuleBase.
399: * @see org.kuali.module.purap.rules.PurapAccountingDocumentRuleBase#checkAccountingLineAccountAccessibility(org.kuali.kfs.document.AccountingDocument,
400: * org.kuali.kfs.bo.AccountingLine, org.kuali.module.purap.rules.PurapAccountingDocumentRuleBase.AccountingLineAction)
401: */
402: @Override
403: protected boolean checkAccountingLineAccountAccessibility(
404: AccountingDocument financialDocument,
405: AccountingLine accountingLine, AccountingLineAction action) {
406: KualiWorkflowDocument workflowDocument = financialDocument
407: .getDocumentHeader().getWorkflowDocument();
408: List currentRouteLevels = getCurrentRouteLevels(workflowDocument);
409:
410: if (((PurchaseOrderDocument) financialDocument)
411: .isDocumentStoppedInRouteNode(NodeDetailEnum.INTERNAL_PURCHASING_REVIEW)) {
412: // DO NOTHING: do not check that user owns acct lines; at this level, approvers can edit all detail on PO
413: return true;
414: } else {
415:
416: return super .checkAccountingLineAccountAccessibility(
417: financialDocument, accountingLine, action);
418: }
419: }
420:
421: /**
422: * @see org.kuali.module.purap.rules.PurapAccountingDocumentRuleBase#customizeExplicitGeneralLedgerPendingEntry(org.kuali.kfs.document.AccountingDocument,
423: * org.kuali.kfs.bo.AccountingLine, org.kuali.kfs.bo.GeneralLedgerPendingEntry)
424: */
425: @Override
426: protected void customizeExplicitGeneralLedgerPendingEntry(
427: AccountingDocument accountingDocument,
428: AccountingLine accountingLine,
429: GeneralLedgerPendingEntry explicitEntry) {
430: super .customizeExplicitGeneralLedgerPendingEntry(
431: accountingDocument, accountingLine, explicitEntry);
432: PurchaseOrderDocument po = (PurchaseOrderDocument) accountingDocument;
433:
434: SpringContext.getBean(PurapGeneralLedgerService.class)
435: .customizeGeneralLedgerPendingEntry(po, accountingLine,
436: explicitEntry, po.getPurapDocumentIdentifier(),
437: GL_DEBIT_CODE, PurapDocTypeCodes.PO_DOCUMENT,
438: true);
439:
440: // don't think i should have to override this, but default isn't getting the right PO doc
441: explicitEntry
442: .setFinancialDocumentTypeCode(PurapDocTypeCodes.PO_DOCUMENT);
443: }
444:
445: }
|