1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.kuali.ole.module.purap.service.impl;
17
18 import org.apache.commons.lang.ArrayUtils;
19 import org.apache.commons.lang.StringUtils;
20 import org.apache.commons.lang.math.NumberUtils;
21 import org.apache.log4j.Logger;
22 import org.kuali.ole.module.purap.PurapConstants;
23 import org.kuali.ole.module.purap.PurapKeyConstants;
24 import org.kuali.ole.module.purap.PurapParameterConstants;
25 import org.kuali.ole.module.purap.batch.ElectronicInvoiceStep;
26 import org.kuali.ole.module.purap.businessobject.*;
27 import org.kuali.ole.module.purap.document.PurchaseOrderDocument;
28 import org.kuali.ole.module.purap.service.ElectronicInvoiceMatchingService;
29 import org.kuali.ole.module.purap.util.ElectronicInvoiceUtils;
30 import org.kuali.ole.module.purap.util.PurApItemUtils;
31 import org.kuali.ole.sys.context.SpringContext;
32 import org.kuali.ole.sys.service.TaxService;
33 import org.kuali.ole.sys.service.impl.OleParameterConstants;
34 import org.kuali.ole.vnd.businessobject.PurchaseOrderCostSource;
35 import org.kuali.ole.vnd.businessobject.VendorDetail;
36 import org.kuali.ole.vnd.document.service.VendorService;
37 import org.kuali.rice.core.api.datetime.DateTimeService;
38 import org.kuali.rice.core.api.util.type.KualiDecimal;
39 import org.kuali.rice.coreservice.framework.parameter.ParameterService;
40 import org.kuali.rice.krad.service.BusinessObjectService;
41 import org.kuali.rice.krad.util.ObjectUtils;
42
43 import java.math.BigDecimal;
44 import java.util.*;
45
46 public class ElectronicInvoiceMatchingServiceImpl implements ElectronicInvoiceMatchingService {
47
48 private Logger LOG = Logger.getLogger(ElectronicInvoiceMatchingServiceImpl.class);
49
50 private Map<String, ElectronicInvoiceRejectReasonType> rejectReasonTypes;
51 private VendorService vendorService;
52 private TaxService taxService;
53 private DateTimeService dateTimeService;
54
55 String upperVariancePercentString;
56 String lowerVariancePercentString;
57
58 public void doMatchingProcess(ElectronicInvoiceOrderHolder orderHolder) {
59
60 if (LOG.isInfoEnabled()) {
61 LOG.info("Matching process started");
62 }
63
64 upperVariancePercentString = SpringContext.getBean(ParameterService.class).getParameterValueAsString(ElectronicInvoiceStep.class, PurapParameterConstants.ElectronicInvoiceParameters.SALES_TAX_UPPER_VARIANCE_PERCENT);
65 lowerVariancePercentString = SpringContext.getBean(ParameterService.class).getParameterValueAsString(ElectronicInvoiceStep.class, PurapParameterConstants.ElectronicInvoiceParameters.SALES_TAX_LOWER_VARIANCE_PERCENT);
66 ;
67
68 try {
69 if (orderHolder.isValidateHeaderInformation()) {
70
71 validateHeaderInformation(orderHolder);
72
73 if (orderHolder.isInvoiceRejected()) {
74 if (LOG.isInfoEnabled()) {
75 LOG.info("Matching process failed at header validation");
76 }
77 return;
78 }
79 }
80
81 validateInvoiceDetails(orderHolder);
82
83 if (orderHolder.isInvoiceRejected()) {
84 if (LOG.isInfoEnabled()) {
85 LOG.info("Matching process failed at order detail validation");
86 }
87 return;
88 }
89
90 } catch (NumberFormatException e) {
91 if (LOG.isInfoEnabled()) {
92 LOG.info("Matching process matching failed due to number format exception " + e.getMessage());
93 }
94 ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.INVALID_NUMBER_FORMAT, e.getMessage(), orderHolder.getFileName());
95 orderHolder.addInvoiceHeaderRejectReason(rejectReason);
96 return;
97 }
98
99 if (LOG.isInfoEnabled()) {
100 LOG.info("Matching process ended successfully");
101 }
102 }
103
104 protected void validateHeaderInformation(ElectronicInvoiceOrderHolder orderHolder) {
105
106 String dunsField = PurapConstants.ElectronicInvoice.RejectDocumentFields.VENDOR_DUNS_NUMBER;
107 String applnResourceKeyName = PurapKeyConstants.ERROR_REJECT_INVALID_DUNS;
108
109 if (StringUtils.isEmpty(orderHolder.getDunsNumber())) {
110 ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.DUNS_NOT_FOUND, null, orderHolder.getFileName());
111 orderHolder.addInvoiceHeaderRejectReason(rejectReason, dunsField, applnResourceKeyName);
112 return;
113 }
114
115 if (orderHolder.isRejectDocumentHolder()) {
116 VendorDetail vendorDetail = SpringContext.getBean(VendorService.class).getVendorByDunsNumber(orderHolder.getDunsNumber());
117 if (vendorDetail == null) {
118 ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.DUNS_INVALID, null, orderHolder.getFileName());
119 orderHolder.addInvoiceHeaderRejectReason(rejectReason, dunsField, applnResourceKeyName);
120 return;
121 }
122 } else {
123 if (orderHolder.getVendorHeaderId() == null && orderHolder.getVendorDetailId() == null) {
124 ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.DUNS_INVALID, null, orderHolder.getFileName());
125 orderHolder.addInvoiceHeaderRejectReason(rejectReason, dunsField, applnResourceKeyName);
126 return;
127 }
128 }
129
130 String invoiceNumberField = PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_FILE_NUMBER;
131 if (!orderHolder.isInvoiceNumberAcceptIndicatorEnabled()) {
132 if (StringUtils.isEmpty(orderHolder.getInvoiceNumber())) {
133 ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.INVOICE_ID_EMPTY, null, orderHolder.getFileName());
134 orderHolder.addInvoiceHeaderRejectReason(rejectReason, invoiceNumberField, PurapKeyConstants.ERROR_REJECT_INVOICE_NUMBER_EMPTY);
135 return;
136 }
137 }
138
139 String invoiceDateField = PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_FILE_DATE;
140
141 if (StringUtils.isEmpty(orderHolder.getInvoiceDateString()) || orderHolder.getInvoiceDate() == null) {
142 ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.INVOICE_DATE_INVALID, null, orderHolder.getFileName());
143 orderHolder.addInvoiceHeaderRejectReason(rejectReason, invoiceDateField, PurapKeyConstants.ERROR_REJECT_INVOICE_DATE_INVALID);
144 return;
145 } else if (orderHolder.getInvoiceDate().after(dateTimeService.getCurrentDate())) {
146 ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.INVOICE_DATE_GREATER, null, orderHolder.getFileName());
147 orderHolder.addInvoiceOrderRejectReason(rejectReason, invoiceDateField, PurapKeyConstants.ERROR_REJECT_INVOICE_DATE_GREATER);
148 return;
149 }
150
151 if (orderHolder.isInformationOnly()) {
152 ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.INFORMATION_ONLY, null, orderHolder.getFileName());
153 orderHolder.addInvoiceHeaderRejectReason(rejectReason);
154 return;
155 }
156
157 validateSummaryAmounts(orderHolder);
158
159 if (orderHolder.isInvoiceRejected()) {
160 return;
161 }
162
163 validateItemTypes(orderHolder);
164
165 if (orderHolder.isInvoiceRejected()) {
166 return;
167 }
168
169 }
170
171 protected void validateSummaryAmounts(ElectronicInvoiceOrderHolder orderHolder) {
172
173 if (orderHolder.isRejectDocumentHolder()) {
174
175
176
177
178 return;
179 }
180
181 ElectronicInvoiceDetailRequestSummary summary = orderHolder.getElectronicInvoice().getInvoiceDetailRequestSummary();
182
183 boolean enableSalesTaxInd = SpringContext.getBean(ParameterService.class).getParameterValueAsBoolean(OleParameterConstants.PURCHASING_DOCUMENT.class, PurapParameterConstants.ENABLE_SALES_TAX_IND);
184
185 boolean salesTaxUsed = false;
186 PurchaseOrderDocument poDoc = orderHolder.getPurchaseOrderDocument();
187 if (poDoc != null) {
188 List<PurApItem> items = PurApItemUtils.getAboveTheLineOnly(poDoc.getItems());
189 for (PurApItem item : items) {
190 if (item.getItemType().isTaxableIndicator()) {
191 salesTaxUsed = true;
192 break;
193 }
194 }
195
196 boolean useTaxUsed = poDoc.isUseTaxIndicator();
197 enableSalesTaxInd &= (salesTaxUsed || useTaxUsed);
198
199 BigDecimal summaryTaxAmount = summary.getInvoiceTaxAmount();
200 if (!enableSalesTaxInd) {
201
202 if (summaryTaxAmount.compareTo(new BigDecimal(0)) != 0) {
203 String extraDescription = "Summary Tax Amount:" + summaryTaxAmount;
204 ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.TAX_SUMMARY_AMT_EXISTS, extraDescription, orderHolder.getFileName());
205 orderHolder.addInvoiceHeaderRejectReason(rejectReason);
206 }
207 } else if (orderHolder.isTaxInLine()) {
208 validateSummaryAmount(orderHolder, summaryTaxAmount, ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_TAX, PurapConstants.ElectronicInvoice.TAX_SUMMARY_AMT_MISMATCH);
209 }
210 }
211
212 if (orderHolder.isShippingInLine()) {
213 validateSummaryAmount(orderHolder, summary.getInvoiceShippingAmount(), ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_SHIPPING, PurapConstants.ElectronicInvoice.SHIPPING_SUMMARY_AMT_MISMATCH);
214 }
215
216 if (orderHolder.isSpecialHandlingInLine()) {
217 validateSummaryAmount(orderHolder, summary.getInvoiceSpecialHandlingAmount(), ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_SPECIAL_HANDLING, PurapConstants.ElectronicInvoice.SPL_HANDLING_SUMMARY_AMT_MISMATCH);
218 }
219
220 if (orderHolder.isDiscountInLine()) {
221 validateSummaryAmount(orderHolder, summary.getInvoiceDiscountAmount(), ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_DISCOUNT, PurapConstants.ElectronicInvoice.DISCOUNT_SUMMARY_AMT_MISMATCH);
222 }
223
224 }
225
226 protected void validateSummaryAmount(ElectronicInvoiceOrderHolder orderHolder,
227 BigDecimal summaryAmount,
228 String invoiceLineItemTypeCode,
229 String rejectDescriptionCode) {
230
231 BigDecimal lineItemTotalAmount = orderHolder.getElectronicInvoice().getFileTotalAmountForInLineItems(invoiceLineItemTypeCode);
232
233
234 if ((lineItemTotalAmount.compareTo(summaryAmount)) != 0) {
235 String extraDescription = "Line Total Amount:" + lineItemTotalAmount + ",Summary Total Amount:" + summaryAmount;
236 ElectronicInvoiceRejectReason rejectReason = createRejectReason(rejectDescriptionCode, extraDescription, orderHolder.getFileName());
237 orderHolder.addInvoiceHeaderRejectReason(rejectReason);
238 }
239
240 }
241
242 protected void validateItemTypes(ElectronicInvoiceOrderHolder orderHolder) {
243
244 validateItemMapping(orderHolder, ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_ITEM);
245 validateItemMapping(orderHolder, ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_TAX);
246 validateItemMapping(orderHolder, ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_SHIPPING);
247 validateItemMapping(orderHolder, ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_SPECIAL_HANDLING);
248 validateItemMapping(orderHolder, ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_DISCOUNT);
249 validateItemMapping(orderHolder, ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_EXMT);
250
251 }
252
253 protected void validateItemMapping(ElectronicInvoiceOrderHolder orderHolder, String kualiItemTypeCode) {
254
255 if (!orderHolder.isItemTypeAvailableInItemMapping(kualiItemTypeCode)) {
256 String extraDescription = kualiItemTypeCode;
257 ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.ITEM_MAPPING_NOT_AVAILABLE, extraDescription, orderHolder.getFileName());
258 orderHolder.addInvoiceHeaderRejectReason(rejectReason);
259 return;
260 }
261
262 }
263
264 protected void validateInvoiceDetails(ElectronicInvoiceOrderHolder orderHolder) {
265
266 validatePurchaseOrderMatch(orderHolder);
267
268 if (orderHolder.isInvoiceRejected()) {
269 return;
270 }
271
272 validateInvoiceItems(orderHolder);
273
274 if (LOG.isInfoEnabled()) {
275 if (!orderHolder.isInvoiceRejected()) {
276 LOG.info("Purchase order document match done successfully");
277 }
278 }
279 }
280
281 protected void validatePurchaseOrderMatch(ElectronicInvoiceOrderHolder orderHolder) {
282
283 String poIDFieldName = PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_PO_ID;
284 String poID = orderHolder.getInvoicePurchaseOrderID();
285
286 if (StringUtils.isEmpty(poID)) {
287 ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.PO_ID_EMPTY, null, orderHolder.getFileName());
288 orderHolder.addInvoiceOrderRejectReason(rejectReason, poIDFieldName, PurapKeyConstants.ERROR_REJECT_INVOICE_POID_EMPTY);
289 return;
290 }
291
292 String extraDesc = "Invoice Order ID:" + poID;
293
294 if (!NumberUtils.isDigits(poID)) {
295 ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.PO_ID_INVALID_FORMAT, extraDesc, orderHolder.getFileName());
296 orderHolder.addInvoiceOrderRejectReason(rejectReason, poIDFieldName, PurapKeyConstants.ERROR_REJECT_INVOICE_POID_INVALID);
297 return;
298 }
299
300 PurchaseOrderDocument poDoc = orderHolder.getPurchaseOrderDocument();
301
302 if (poDoc == null) {
303 ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.PO_NOT_EXISTS, extraDesc, orderHolder.getFileName());
304 orderHolder.addInvoiceOrderRejectReason(rejectReason, poIDFieldName, PurapKeyConstants.ERROR_REJECT_INVOICE__PO_NOT_EXISTS);
305 return;
306 }
307
308 if (poDoc.getVendorHeaderGeneratedIdentifier() == null ||
309 poDoc.getVendorDetailAssignedIdentifier() == null ||
310 !(poDoc.getVendorHeaderGeneratedIdentifier().equals(orderHolder.getVendorHeaderId()) &&
311 poDoc.getVendorDetailAssignedIdentifier().equals(orderHolder.getVendorDetailId()))) {
312 ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.PO_VENDOR_NOT_MATCHES_WITH_INVOICE_VENDOR, null, orderHolder.getFileName());
313 orderHolder.addInvoiceOrderRejectReason(rejectReason);
314 return;
315 }
316
317 }
318
319 protected void validateInvoiceItems(ElectronicInvoiceOrderHolder orderHolder) {
320
321 Set poLineNumbers = new HashSet();
322
323 ElectronicInvoiceItemHolder[] itemHolders = orderHolder.getItems();
324 if (itemHolders != null) {
325 for (int i = 0; i < itemHolders.length; i++) {
326 validateInvoiceItem(itemHolders[i], poLineNumbers);
327 }
328 }
329 }
330
331 protected void validateInvoiceItem(ElectronicInvoiceItemHolder itemHolder,
332 Set poLineNumbers) {
333
334 PurchaseOrderItem poItem = itemHolder.getPurchaseOrderItem();
335 ElectronicInvoiceOrderHolder orderHolder = itemHolder.getInvoiceOrderHolder();
336
337 if (poItem == null) {
338 String extraDescription = "Invoice Item Line Number:" + itemHolder.getInvoiceItemLineNumber();
339 ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.NO_MATCHING_PO_ITEM, extraDescription, orderHolder.getFileName());
340 orderHolder.addInvoiceOrderRejectReason(rejectReason, PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_ITEM_LINE_NUMBER, PurapKeyConstants.ERROR_REJECT_INVOICE__ITEM_NOMATCH);
341 return;
342 }
343
344 if (poLineNumbers.contains(itemHolder.getInvoiceItemLineNumber())) {
345 String extraDescription = "Invoice Item Line Number:" + itemHolder.getInvoiceItemLineNumber();
346 ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.DUPLIATE_INVOICE_LINE_ITEM, extraDescription, orderHolder.getFileName());
347 orderHolder.addInvoiceOrderRejectReason(rejectReason, PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_ITEM_LINE_NUMBER, PurapKeyConstants.ERROR_REJECT_PO_ITEM_DUPLICATE);
348 return;
349 } else {
350 poLineNumbers.add(itemHolder.getInvoiceItemLineNumber());
351 }
352
353 if (!poItem.isItemActiveIndicator()) {
354 String extraDescription = "PO Item Line Number:" + poItem.getItemLineNumber();
355 ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.INACTIVE_LINE_ITEM, extraDescription, orderHolder.getFileName());
356 orderHolder.addInvoiceOrderRejectReason(rejectReason, PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_ITEM_LINE_NUMBER, PurapKeyConstants.ERROR_REJECT_PO_ITEM_INACTIVE);
357 return;
358 }
359
360 if (!itemHolder.isCatalogNumberAcceptIndicatorEnabled()) {
361 validateCatalogNumber(itemHolder);
362 if (orderHolder.isInvoiceRejected()) {
363 return;
364 }
365 }
366
367 if (!itemHolder.isUnitOfMeasureAcceptIndicatorEnabled()) {
368 if (!StringUtils.equals(poItem.getItemUnitOfMeasureCode(), itemHolder.getInvoiceItemUnitOfMeasureCode())) {
369 String extraDescription = "Invoice UOM:" + itemHolder.getInvoiceItemUnitOfMeasureCode() + ", PO UOM:" + poItem.getItemUnitOfMeasureCode();
370 ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.UNIT_OF_MEASURE_MISMATCH, extraDescription, orderHolder.getFileName());
371 orderHolder.addInvoiceOrderRejectReason(rejectReason, PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_ITEM_UOM, PurapKeyConstants.ERROR_REJECT_UOM_MISMATCH);
372 return;
373 }
374 }
375
376 validateUnitPrice(itemHolder);
377
378 if (orderHolder.isInvoiceRejected()) {
379 return;
380 }
381
382 validateSalesTax(itemHolder);
383
384 if (orderHolder.isInvoiceRejected()) {
385 return;
386 }
387
388 if (poItem.getItemQuantity() != null) {
389 validateQtyBasedItem(itemHolder);
390 } else {
391 validateNonQtyBasedItem(itemHolder);
392 }
393
394 }
395
396 protected void validateCatalogNumber(ElectronicInvoiceItemHolder itemHolder) {
397
398 PurchaseOrderItem poItem = itemHolder.getPurchaseOrderItem();
399 ElectronicInvoiceOrderHolder orderHolder = itemHolder.getInvoiceOrderHolder();
400
401 String invoiceCatalogNumberStripped = itemHolder.getCatalogNumberStripped();
402 String poCatalogNumberStripped = ElectronicInvoiceUtils.stripSplChars(poItem.getItemCatalogNumber());
403
404
405
406
407 if (StringUtils.isNotBlank(invoiceCatalogNumberStripped) &&
408 StringUtils.isNotBlank(poCatalogNumberStripped)) {
409
410 if (!StringUtils.equals(poCatalogNumberStripped, invoiceCatalogNumberStripped)) {
411
412 String extraDescription = "Invoice Catalog No:" + invoiceCatalogNumberStripped + ", PO Catalog No:" + poCatalogNumberStripped;
413 ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.CATALOG_NUMBER_MISMATCH, extraDescription, orderHolder.getFileName());
414 orderHolder.addInvoiceOrderRejectReason(rejectReason, PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_ITEM_CATALOG_NUMBER, PurapKeyConstants.ERROR_REJECT_CATALOG_MISMATCH);
415 }
416
417 } else {
418
419
420
421
422
423
424 String reqSourceRequiringCatalogMatch = SpringContext.getBean(ParameterService.class).getParameterValueAsString(ElectronicInvoiceStep.class, PurapParameterConstants.ElectronicInvoiceParameters.REQUISITION_SOURCES_REQUIRING_CATALOG_MATCHING);
425 String requisitionSourceCodeInPO = orderHolder.getPurchaseOrderDocument().getRequisitionSourceCode();
426
427 if (StringUtils.isNotEmpty(reqSourceRequiringCatalogMatch)) {
428 String[] requisitionSourcesFromParam = StringUtils.split(reqSourceRequiringCatalogMatch, ';');
429 if (ArrayUtils.contains(requisitionSourcesFromParam, requisitionSourceCodeInPO)) {
430 String extraDescription = "Invoice Catalog No:" + invoiceCatalogNumberStripped + ", PO Catalog No:" + poItem.getItemCatalogNumber();
431 ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.CATALOG_NUMBER_MISMATCH, extraDescription, orderHolder.getFileName());
432 orderHolder.addInvoiceOrderRejectReason(rejectReason, PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_ITEM_CATALOG_NUMBER, PurapKeyConstants.ERROR_REJECT_CATALOG_MISMATCH);
433 }
434 }
435 }
436 }
437
438 protected void validateQtyBasedItem(ElectronicInvoiceItemHolder itemHolder) {
439
440 PurchaseOrderItem poItem = itemHolder.getPurchaseOrderItem();
441
442 String fileName = itemHolder.getInvoiceOrderHolder().getFileName();
443 ElectronicInvoiceOrderHolder orderHolder = itemHolder.getInvoiceOrderHolder();
444
445 if (KualiDecimal.ZERO.compareTo(poItem.getItemOutstandingEncumberedQuantity()) >= 0) {
446
447 String extraDescription = "Invoice Item Line Number:" + itemHolder.getInvoiceItemLineNumber();
448 ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.OUTSTANDING_ENCUMBERED_QTY_AVAILABLE, extraDescription, orderHolder.getFileName());
449 orderHolder.addInvoiceOrderRejectReason(rejectReason, PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_ITEM_QUANTITY, PurapKeyConstants.ERROR_REJECT_POITEM_OUTSTANDING_QTY);
450 return;
451 }
452
453 if (itemHolder.getInvoiceItemQuantity() == null) {
454
455 String extraDescription = "Invoice Item Line Number:" + itemHolder.getInvoiceItemLineNumber();
456 ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.INVOICE_QTY_EMPTY, extraDescription, orderHolder.getFileName());
457 orderHolder.addInvoiceOrderRejectReason(rejectReason, PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_ITEM_QUANTITY, PurapKeyConstants.ERROR_REJECT_POITEM_INVOICE_QTY_EMPTY);
458 return;
459 } else {
460
461 if (!itemHolder.getInvoiceOrderHolder().getPurchaseOrderDocument().isReceivingDocumentRequiredIndicator()) {
462
463 if ((itemHolder.getInvoiceItemQuantity().compareTo(poItem.getItemOutstandingEncumberedQuantity().bigDecimalValue())) > 0) {
464
465 String extraDescription = "Invoice Item Line Number:" + itemHolder.getInvoiceItemLineNumber();
466 ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.PO_ITEM_QTY_LESSTHAN_INVOICE_ITEM_QTY, extraDescription, orderHolder.getFileName());
467 orderHolder.addInvoiceOrderRejectReason(rejectReason, PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_ITEM_QUANTITY, PurapKeyConstants.ERROR_REJECT_POITEM_LESS_OUTSTANDING_QTY);
468 return;
469 }
470 }
471 }
472
473 }
474
475 protected void validateNonQtyBasedItem(ElectronicInvoiceItemHolder itemHolder) {
476
477 PurchaseOrderItem poItem = itemHolder.getPurchaseOrderItem();
478
479 String fileName = itemHolder.getInvoiceOrderHolder().getFileName();
480 ElectronicInvoiceOrderHolder orderHolder = itemHolder.getInvoiceOrderHolder();
481
482 if ((KualiDecimal.ZERO.compareTo(poItem.getItemOutstandingEncumberedAmount())) >= 0) {
483
484 String extraDescription = "Invoice Item Line Number:" + itemHolder.getInvoiceItemLineNumber();
485 ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.OUTSTANDING_ENCUMBERED_AMT_AVAILABLE, extraDescription, orderHolder.getFileName());
486 orderHolder.addInvoiceOrderRejectReason(rejectReason, PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_ITEM_LINE_NUMBER, PurapKeyConstants.ERROR_REJECT_POITEM_OUTSTANDING_EMCUMBERED_AMOUNT);
487 return;
488 } else {
489
490 if (((itemHolder.getInvoiceItemSubTotalAmount().setScale(KualiDecimal.SCALE, KualiDecimal.ROUND_BEHAVIOR)).compareTo(poItem.getItemOutstandingEncumberedAmount().bigDecimalValue())) > 0) {
491 String extraDescription = "Invoice Item Line Number:" + itemHolder.getInvoiceItemLineNumber();
492 ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.PO_ITEM_AMT_LESSTHAN_INVOICE_ITEM_AMT, extraDescription, orderHolder.getFileName());
493 orderHolder.addInvoiceOrderRejectReason(rejectReason, PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_ITEM_LINE_NUMBER, PurapKeyConstants.ERROR_REJECT_POITEM_LESS_OUTSTANDING_EMCUMBERED_AMOUNT);
494 return;
495 }
496
497 }
498 }
499
500 protected void validateUnitPrice(ElectronicInvoiceItemHolder itemHolder) {
501
502 PurchaseOrderCostSource costSource = itemHolder.getInvoiceOrderHolder().getPurchaseOrderDocument().getPurchaseOrderCostSource();
503 PurchaseOrderItem poItem = itemHolder.getPurchaseOrderItem();
504 ElectronicInvoiceOrderHolder orderHolder = itemHolder.getInvoiceOrderHolder();
505
506 String extraDescription = "Invoice Item Line Number:" + itemHolder.getInvoiceItemLineNumber();
507
508 BigDecimal actualVariance = itemHolder.getInvoiceItemUnitPrice().subtract(poItem.getItemUnitPrice());
509
510 BigDecimal lowerPercentage = null;
511 if (costSource.getItemUnitPriceLowerVariancePercent() != null) {
512
513 lowerPercentage = costSource.getItemUnitPriceLowerVariancePercent();
514 } else {
515
516
517 lowerPercentage = new BigDecimal(100);
518 }
519
520 BigDecimal lowerAcceptableVariance = (lowerPercentage.divide(new BigDecimal(100))).multiply(poItem.getItemUnitPrice()).negate();
521
522 if (lowerAcceptableVariance.compareTo(actualVariance) > 0) {
523 ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.INVOICE_AMT_LESSER_THAN_LOWER_VARIANCE, extraDescription, orderHolder.getFileName());
524 orderHolder.addInvoiceOrderRejectReason(rejectReason, PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_ITEM_UNIT_PRICE, PurapKeyConstants.ERROR_REJECT_UNITPRICE_LOWERVARIANCE);
525 }
526
527 BigDecimal upperPercentage = null;
528
529 if (costSource.getItemUnitPriceUpperVariancePercent() != null) {
530
531 upperPercentage = costSource.getItemUnitPriceUpperVariancePercent();
532 } else {
533
534
535 upperPercentage = new BigDecimal(100);
536 }
537 BigDecimal upperAcceptableVariance = (upperPercentage.divide(new BigDecimal(100))).multiply(poItem.getItemUnitPrice());
538
539 if (upperAcceptableVariance.compareTo(actualVariance) < 0) {
540 ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.INVOICE_AMT_GREATER_THAN_UPPER_VARIANCE, extraDescription, orderHolder.getFileName());
541 orderHolder.addInvoiceOrderRejectReason(rejectReason, PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_ITEM_UNIT_PRICE, PurapKeyConstants.ERROR_REJECT_UNITPRICE_UPPERVARIANCE);
542 }
543
544 }
545
546 protected void validateSalesTax(ElectronicInvoiceItemHolder itemHolder) {
547
548 if (LOG.isInfoEnabled()) {
549 LOG.info("Validating sales tax");
550 }
551
552 ElectronicInvoiceOrderHolder orderHolder = itemHolder.getInvoiceOrderHolder();
553 PurchaseOrderItem poItem = itemHolder.getPurchaseOrderItem();
554 KualiDecimal invoiceSalesTaxAmount = new KualiDecimal(itemHolder.getTaxAmount());
555
556 boolean enableSalesTaxInd = SpringContext.getBean(ParameterService.class).getParameterValueAsBoolean(OleParameterConstants.PURCHASING_DOCUMENT.class, PurapParameterConstants.ENABLE_SALES_TAX_IND);
557
558 boolean salesTaxUsed = false;
559 PurchaseOrderDocument poDoc = orderHolder.getPurchaseOrderDocument();
560 List<PurApItem> items = PurApItemUtils.getAboveTheLineOnly(poDoc.getItems());
561 for (PurApItem item : items) {
562 if (item.getItemType().isTaxableIndicator()) {
563 salesTaxUsed = true;
564 break;
565 }
566 }
567 boolean useTaxUsed = poDoc.isUseTaxIndicator();
568 enableSalesTaxInd &= (poItem.getItemType().isTaxableIndicator() && (salesTaxUsed || useTaxUsed));
569
570 if (LOG.isInfoEnabled()) {
571 LOG.info("Sales Tax Enable Indicator - " + enableSalesTaxInd);
572 LOG.info("Invoice item tax amount - " + invoiceSalesTaxAmount);
573 }
574 if (!enableSalesTaxInd) {
575
576 if (invoiceSalesTaxAmount.compareTo(KualiDecimal.ZERO) != 0) {
577 String extraDescription = "Item Tax Amount:" + invoiceSalesTaxAmount;
578 ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.TAX_SUMMARY_AMT_EXISTS, extraDescription, orderHolder.getFileName());
579 orderHolder.addInvoiceHeaderRejectReason(rejectReason);
580 }
581 return;
582 }
583
584
585 java.sql.Date transTaxDate = itemHolder.getInvoiceOrderHolder().getInvoiceProcessedDate();
586 String deliveryPostalCode = poItem.getPurchaseOrder().getDeliveryPostalCode();
587 KualiDecimal extendedPrice = new KualiDecimal(getExtendedPrice(itemHolder).setScale(KualiDecimal.SCALE, KualiDecimal.ROUND_BEHAVIOR));
588
589 KualiDecimal salesTaxAmountCalculated = taxService.getTotalSalesTaxAmount(transTaxDate, deliveryPostalCode, extendedPrice);
590 KualiDecimal actualVariance = invoiceSalesTaxAmount.subtract(salesTaxAmountCalculated);
591
592 if (LOG.isInfoEnabled()) {
593 LOG.info("Sales Tax Upper Variance param - " + upperVariancePercentString);
594 LOG.info("Sales Tax Lower Variance param - " + lowerVariancePercentString);
595 LOG.info("Trans date (from invoice/rejectdoc) - " + transTaxDate);
596 LOG.info("Delivery Postal Code - " + deliveryPostalCode);
597 LOG.info("Extended price - " + extendedPrice);
598 LOG.info("Sales Tax amount (from sales tax service) - " + salesTaxAmountCalculated);
599 }
600
601 if (StringUtils.isNotEmpty(upperVariancePercentString)) {
602
603 KualiDecimal upperVariancePercent = new KualiDecimal(upperVariancePercentString);
604 BigDecimal upperAcceptableVariance = (upperVariancePercent.divide(new KualiDecimal(100))).multiply(salesTaxAmountCalculated).bigDecimalValue();
605
606 if (upperAcceptableVariance.compareTo(actualVariance.bigDecimalValue()) < 0) {
607 ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.SALES_TAX_AMT_GREATER_THAN_UPPER_VARIANCE, null, orderHolder.getFileName());
608 orderHolder.addInvoiceOrderRejectReason(rejectReason, PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_ITEM_TAX_AMT, PurapKeyConstants.ERROR_REJECT_TAXAMOUNT_UPPERVARIANCE);
609 return;
610 }
611
612 }
613
614 if (StringUtils.isNotEmpty(lowerVariancePercentString)) {
615
616 KualiDecimal lowerVariancePercent = new KualiDecimal(lowerVariancePercentString);
617 BigDecimal lowerAcceptableVariance = (lowerVariancePercent.divide(new KualiDecimal(100))).multiply(salesTaxAmountCalculated).bigDecimalValue().negate();
618
619 if (lowerAcceptableVariance.compareTo(BigDecimal.ZERO) >= 0 &&
620 actualVariance.compareTo(KualiDecimal.ZERO) >= 0) {
621 if (actualVariance.bigDecimalValue().compareTo(lowerAcceptableVariance) > 0) {
622 ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.SALES_TAX_AMT_LESSER_THAN_LOWER_VARIANCE, null, orderHolder.getFileName());
623 orderHolder.addInvoiceOrderRejectReason(rejectReason, PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_ITEM_TAX_AMT, PurapKeyConstants.ERROR_REJECT_TAXAMOUNT_LOWERVARIANCE);
624 }
625 } else {
626 if (actualVariance.bigDecimalValue().compareTo(lowerAcceptableVariance) < 0) {
627 ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.SALES_TAX_AMT_LESSER_THAN_LOWER_VARIANCE, null, orderHolder.getFileName());
628 orderHolder.addInvoiceOrderRejectReason(rejectReason, PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_ITEM_TAX_AMT, PurapKeyConstants.ERROR_REJECT_TAXAMOUNT_LOWERVARIANCE);
629 }
630 }
631 }
632
633 }
634
635
636
637 protected BigDecimal getExtendedPrice(ElectronicInvoiceItemHolder itemHolder) {
638 if (itemHolder.getPurchaseOrderItem().getItemType().isAmountBasedGeneralLedgerIndicator()) {
639
640 return itemHolder.getUnitPrice();
641 } else if (ObjectUtils.isNotNull(itemHolder.getQuantity())) {
642 BigDecimal calcExtendedPrice = itemHolder.getUnitPrice().multiply(itemHolder.getQuantity());
643
644 return calcExtendedPrice;
645 }
646 return BigDecimal.ZERO;
647 }
648
649 public ElectronicInvoiceRejectReason createRejectReason(String rejectReasonTypeCode, String extraDescription, String fileName) {
650
651 ElectronicInvoiceRejectReasonType rejectReasonType = getElectronicInvoiceRejectReasonType(rejectReasonTypeCode);
652 ElectronicInvoiceRejectReason eInvoiceRejectReason = new ElectronicInvoiceRejectReason();
653
654 if (rejectReasonType == null) {
655 throw new NullPointerException("Reject reason type for " + rejectReasonTypeCode + " not available in DB");
656 }
657 eInvoiceRejectReason.setInvoiceFileName(fileName);
658 eInvoiceRejectReason.setInvoiceRejectReasonTypeCode(rejectReasonTypeCode);
659
660 if (StringUtils.isNotEmpty(extraDescription)) {
661 eInvoiceRejectReason.setInvoiceRejectReasonDescription(rejectReasonType.getInvoiceRejectReasonTypeDescription() + " (" + extraDescription + ")");
662 } else {
663 eInvoiceRejectReason.setInvoiceRejectReasonDescription(rejectReasonType.getInvoiceRejectReasonTypeDescription());
664 }
665
666 return eInvoiceRejectReason;
667
668 }
669
670 public ElectronicInvoiceRejectReasonType getElectronicInvoiceRejectReasonType(String rejectReasonTypeCode) {
671 if (rejectReasonTypes == null) {
672 rejectReasonTypes = getElectronicInvoiceRejectReasonTypes();
673 }
674 return rejectReasonTypes.get(rejectReasonTypeCode);
675 }
676
677 protected Map<String, ElectronicInvoiceRejectReasonType> getElectronicInvoiceRejectReasonTypes() {
678
679 Collection<ElectronicInvoiceRejectReasonType> collection = SpringContext.getBean(BusinessObjectService.class).findAll(ElectronicInvoiceRejectReasonType.class);
680 Map rejectReasonTypesMap = new HashMap<String, ElectronicInvoiceRejectReasonType>();
681
682 if (collection != null &&
683 collection.size() > 0) {
684 ElectronicInvoiceRejectReasonType[] rejectReasonTypesArr = new ElectronicInvoiceRejectReasonType[collection.size()];
685 collection.toArray(rejectReasonTypesArr);
686 for (int i = 0; i < rejectReasonTypesArr.length; i++) {
687 rejectReasonTypesMap.put(rejectReasonTypesArr[i].getInvoiceRejectReasonTypeCode(), rejectReasonTypesArr[i]);
688 }
689 }
690
691 return rejectReasonTypesMap;
692 }
693
694 public void setVendorService(VendorService vendorService) {
695 this.vendorService = vendorService;
696 }
697
698 public void setTaxService(TaxService taxService) {
699 this.taxService = taxService;
700 }
701
702 public void setDateTimeService(DateTimeService dateTimeService) {
703 this.dateTimeService = dateTimeService;
704 }
705
706 }