001/* 002 * Copyright 2006 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.service.impl; 017 018import org.apache.commons.lang.StringUtils; 019import org.kuali.ole.module.purap.*; 020import org.kuali.ole.module.purap.PurapConstants.PurchaseOrderStatuses; 021import org.kuali.ole.module.purap.PurapParameterConstants.TaxParameters; 022import org.kuali.ole.module.purap.businessobject.*; 023import org.kuali.ole.module.purap.document.*; 024import org.kuali.ole.module.purap.document.service.LogicContainer; 025import org.kuali.ole.module.purap.document.service.PurapService; 026import org.kuali.ole.module.purap.document.service.PurchaseOrderService; 027import org.kuali.ole.module.purap.service.PurapAccountingService; 028import org.kuali.ole.module.purap.util.PurApItemUtils; 029import org.kuali.ole.select.businessobject.OleInvoiceItem; 030import org.kuali.ole.select.document.OleInvoiceDocument; 031import org.kuali.ole.sys.OLEConstants; 032import org.kuali.ole.sys.OLEPropertyConstants; 033import org.kuali.ole.sys.businessobject.SourceAccountingLine; 034import org.kuali.ole.sys.businessobject.TaxDetail; 035import org.kuali.ole.sys.context.SpringContext; 036import org.kuali.ole.sys.document.validation.event.DocumentSystemSaveEvent; 037import org.kuali.ole.sys.service.NonTransactional; 038import org.kuali.ole.sys.service.TaxService; 039import org.kuali.ole.sys.service.UniversityDateService; 040import org.kuali.ole.sys.service.impl.OleParameterConstants; 041import org.kuali.ole.vnd.businessobject.CommodityCode; 042import org.kuali.ole.vnd.document.service.VendorService; 043import org.kuali.rice.core.api.datetime.DateTimeService; 044import org.kuali.rice.core.api.parameter.ParameterEvaluator; 045import org.kuali.rice.core.api.parameter.ParameterEvaluatorService; 046import org.kuali.rice.core.api.util.type.KualiDecimal; 047import org.kuali.rice.coreservice.framework.parameter.ParameterService; 048import org.kuali.rice.kew.api.KewApiServiceLocator; 049import org.kuali.rice.kew.api.WorkflowDocument; 050import org.kuali.rice.kew.api.document.attribute.DocumentAttributeIndexingQueue; 051import org.kuali.rice.kew.api.exception.WorkflowException; 052import org.kuali.rice.kns.service.DataDictionaryService; 053import org.kuali.rice.kns.util.KNSGlobalVariables; 054import org.kuali.rice.krad.UserSession; 055import org.kuali.rice.krad.bo.Note; 056import org.kuali.rice.krad.document.Document; 057import org.kuali.rice.krad.service.BusinessObjectService; 058import org.kuali.rice.krad.service.DocumentService; 059import org.kuali.rice.krad.service.NoteService; 060import org.kuali.rice.krad.service.PersistenceService; 061import org.kuali.rice.krad.util.GlobalVariables; 062import org.kuali.rice.krad.util.KRADPropertyConstants; 063import org.kuali.rice.krad.util.ObjectUtils; 064import org.springframework.transaction.annotation.Transactional; 065 066import java.math.BigDecimal; 067import java.sql.Date; 068import java.sql.Timestamp; 069import java.util.*; 070 071@NonTransactional 072public class PurapServiceImpl implements PurapService { 073 private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(PurapServiceImpl.class); 074 075 private BusinessObjectService businessObjectService; 076 private DataDictionaryService dataDictionaryService; 077 private DateTimeService dateTimeService; 078 private DocumentService documentService; 079 private NoteService noteService; 080 private ParameterService parameterService; 081 private PersistenceService persistenceService; 082 private PurchaseOrderService purchaseOrderService; 083 private UniversityDateService universityDateService; 084 private VendorService vendorService; 085 private TaxService taxService; 086 private PurapAccountingService purapAccountingService; 087 088 public void setBusinessObjectService(BusinessObjectService boService) { 089 this.businessObjectService = boService; 090 } 091 092 public void setDateTimeService(DateTimeService dateTimeService) { 093 this.dateTimeService = dateTimeService; 094 } 095 096 public void setParameterService(ParameterService parameterService) { 097 this.parameterService = parameterService; 098 } 099 100 public void setDocumentService(DocumentService documentService) { 101 this.documentService = documentService; 102 } 103 104 public void setDataDictionaryService(DataDictionaryService dataDictionaryService) { 105 this.dataDictionaryService = dataDictionaryService; 106 } 107 108 public void setVendorService(VendorService vendorService) { 109 this.vendorService = vendorService; 110 } 111 112 public void setPersistenceService(PersistenceService persistenceService) { 113 this.persistenceService = persistenceService; 114 } 115 116 public void setPurchaseOrderService(PurchaseOrderService purchaseOrderService) { 117 this.purchaseOrderService = purchaseOrderService; 118 } 119 120 public void setNoteService(NoteService noteService) { 121 this.noteService = noteService; 122 } 123 124 public void setUniversityDateService(UniversityDateService universityDateService) { 125 this.universityDateService = universityDateService; 126 } 127 128 public void setTaxService(TaxService taxService) { 129 this.taxService = taxService; 130 } 131 132 /** 133 * @see org.kuali.ole.module.purap.document.service.PurapService# updateStatus(org.kuali.ole.module.purap.document.PurchasingAccountsPayableDocument, String) 134 */ 135 //TODO hjs: is this method really needed now that we don't have status history tables? 136 /* @Override 137 public boolean updateStatus(PurchasingAccountsPayableDocument document, String newStatus) { 138 LOG.debug("updateStatus() started"); 139 140 if (ObjectUtils.isNotNull(document) || ObjectUtils.isNotNull(newStatus)) { 141 String oldStatus = document.getApplicationDocumentStatus(); 142 document.setApplicationDocumentStatus(newStatus); 143 if ( LOG.isDebugEnabled() ) { 144 LOG.debug("Status of document #" + document.getDocumentNumber() + " has been changed from " + oldStatus + " to " + newStatus); 145 } 146 return true; 147 } 148 else { 149 return false; 150 } 151 }*/ 152 @Override 153 public void saveRoutingDataForRelatedDocuments(Integer accountsPayablePurchasingDocumentLinkIdentifier) { 154 155 try { 156 //save requisition routing data 157 List<RequisitionView> reqViews = getRelatedViews(RequisitionView.class, accountsPayablePurchasingDocumentLinkIdentifier); 158 for (RequisitionView view : reqViews) { 159 Document doc = documentService.getByDocumentHeaderId(view.getDocumentNumber()); 160 doc.getDocumentHeader().getWorkflowDocument().saveDocumentData(); 161 } 162 163 //save purchase order routing data 164 List<PurchaseOrderView> poViews = getRelatedViews(PurchaseOrderView.class, accountsPayablePurchasingDocumentLinkIdentifier); 165 for (PurchaseOrderView view : poViews) { 166 Document doc = documentService.getByDocumentHeaderId(view.getDocumentNumber()); 167 doc.getDocumentHeader().getWorkflowDocument().saveDocumentData(); 168 } 169 170 //save payment request routing data 171 List<PaymentRequestView> preqViews = getRelatedViews(PaymentRequestView.class, accountsPayablePurchasingDocumentLinkIdentifier); 172 for (PaymentRequestView view : preqViews) { 173 Document doc = documentService.getByDocumentHeaderId(view.getDocumentNumber()); 174 doc.getDocumentHeader().getWorkflowDocument().saveDocumentData(); 175 } 176 177 //save Invoice routing data 178 List<InvoiceView> invViews = getRelatedViews(InvoiceView.class, accountsPayablePurchasingDocumentLinkIdentifier); 179 for (InvoiceView invView : invViews) { 180 Document doc = documentService.getByDocumentHeaderId(invView.getDocumentNumber()); 181 doc.getDocumentHeader().getWorkflowDocument().saveDocumentData(); 182 } 183 184 //save credit memo routing data 185 List<CreditMemoView> cmViews = getRelatedViews(CreditMemoView.class, accountsPayablePurchasingDocumentLinkIdentifier); 186 for (CreditMemoView view : cmViews) { 187 Document doc = documentService.getByDocumentHeaderId(view.getDocumentNumber()); 188 doc.getDocumentHeader().getWorkflowDocument().saveDocumentData(); 189 } 190 191 //save line item receiving routing data 192 List<LineItemReceivingView> lineViews = getRelatedViews(LineItemReceivingView.class, accountsPayablePurchasingDocumentLinkIdentifier); 193 for (LineItemReceivingView view : lineViews) { 194 Document doc = documentService.getByDocumentHeaderId(view.getDocumentNumber()); 195 doc.getDocumentHeader().getWorkflowDocument().saveDocumentData(); 196 } 197 198 //save correction receiving routing data 199 List<CorrectionReceivingView> corrViews = getRelatedViews(CorrectionReceivingView.class, accountsPayablePurchasingDocumentLinkIdentifier); 200 for (CorrectionReceivingView view : corrViews) { 201 Document doc = documentService.getByDocumentHeaderId(view.getDocumentNumber()); 202 doc.getDocumentHeader().getWorkflowDocument().saveDocumentData(); 203 } 204 205 //save bulk receiving routing data 206 List<BulkReceivingView> bulkViews = getRelatedViews(BulkReceivingView.class, accountsPayablePurchasingDocumentLinkIdentifier); 207 for (BulkReceivingView view : bulkViews) { 208 Document doc = documentService.getByDocumentHeaderId(view.getDocumentNumber()); 209 doc.getDocumentHeader().getWorkflowDocument().saveDocumentData(); 210 } 211 } catch (WorkflowException e) { 212 throw new IllegalArgumentException("unable to save routing data for related docs", e); 213 } 214 215 } 216 217 /** 218 * @see org.kuali.ole.module.purap.document.service.PurapService#getRelatedDocumentIds(Integer) 219 */ 220 @Override 221 public List<String> getRelatedDocumentIds(Integer accountsPayablePurchasingDocumentLinkIdentifier) { 222 LOG.debug("getRelatedDocumentIds() started"); 223 List<String> documentIdList = new ArrayList<String>(); 224 225 //get requisition views 226 List<RequisitionView> reqViews = getRelatedViews(RequisitionView.class, accountsPayablePurchasingDocumentLinkIdentifier); 227 for (RequisitionView view : reqViews) { 228 documentIdList.add(view.getDocumentNumber()); 229 } 230 231 //get purchase order views 232 List<PurchaseOrderView> poViews = getRelatedViews(PurchaseOrderView.class, accountsPayablePurchasingDocumentLinkIdentifier); 233 for (PurchaseOrderView view : poViews) { 234 documentIdList.add(view.getDocumentNumber()); 235 } 236 237 //get payment request views 238 List<PaymentRequestView> preqViews = getRelatedViews(PaymentRequestView.class, accountsPayablePurchasingDocumentLinkIdentifier); 239 for (PaymentRequestView view : preqViews) { 240 documentIdList.add(view.getDocumentNumber()); 241 } 242 243 //get Invoice views 244 List<InvoiceView> invViews = getRelatedViews(InvoiceView.class, accountsPayablePurchasingDocumentLinkIdentifier); 245 for (InvoiceView invView : invViews) { 246 documentIdList.add(invView.getDocumentNumber()); 247 } 248 249 //get credit memo views 250 List<CreditMemoView> cmViews = getRelatedViews(CreditMemoView.class, accountsPayablePurchasingDocumentLinkIdentifier); 251 for (CreditMemoView view : cmViews) { 252 documentIdList.add(view.getDocumentNumber()); 253 } 254 255 //get line item receiving views 256 List<LineItemReceivingView> lineViews = getRelatedViews(LineItemReceivingView.class, accountsPayablePurchasingDocumentLinkIdentifier); 257 for (LineItemReceivingView view : lineViews) { 258 documentIdList.add(view.getDocumentNumber()); 259 } 260 261 //get correction receiving views 262 List<CorrectionReceivingView> corrViews = getRelatedViews(CorrectionReceivingView.class, accountsPayablePurchasingDocumentLinkIdentifier); 263 for (CorrectionReceivingView view : corrViews) { 264 documentIdList.add(view.getDocumentNumber()); 265 } 266 267 //get bulk receiving views 268 List<BulkReceivingView> bulkViews = getRelatedViews(BulkReceivingView.class, accountsPayablePurchasingDocumentLinkIdentifier); 269 for (BulkReceivingView view : bulkViews) { 270 documentIdList.add(view.getDocumentNumber()); 271 } 272 273 //TODO (hjs)get electronic invoice reject views??? 274 275 return documentIdList; 276 } 277 278 /** 279 * @see org.kuali.ole.module.purap.document.service.PurapService#getRelatedViews(Class, Integer) 280 */ 281 @Override 282 @SuppressWarnings("unchecked") 283 @Transactional 284 public List getRelatedViews(Class clazz, Integer accountsPayablePurchasingDocumentLinkIdentifier) { 285 LOG.debug("getRelatedViews() started"); 286 287 Map criteria = new HashMap(); 288 String st = clazz.getSimpleName(); 289 290 //if (st.equals("InvoiceView") || st.equals("CreditMemoView") || st.equals("PaymentRequestView")) { 291 if (st.equals("InvoiceView")) { 292 Map criteriaMap = new HashMap(); 293 criteriaMap.put("accountsPayablePurchasingDocumentLinkIdentifier", accountsPayablePurchasingDocumentLinkIdentifier); 294 List<OleInvoiceItem> boInvoiceItemList = (List<OleInvoiceItem>) businessObjectService.findMatching(OleInvoiceItem.class, criteriaMap); 295 List boList = new ArrayList(); 296 if (boInvoiceItemList.size() > 0) { 297 for (OleInvoiceItem item : boInvoiceItemList) { 298 accountsPayablePurchasingDocumentLinkIdentifier =item.getInvoiceDocument().getAccountsPayablePurchasingDocumentLinkIdentifier(); 299 criteria.put("accountsPayablePurchasingDocumentLinkIdentifier", accountsPayablePurchasingDocumentLinkIdentifier); 300 boList.addAll((List) businessObjectService.findMatchingOrderBy(clazz, criteria, OLEPropertyConstants.DOCUMENT_NUMBER, false)); 301 302 } 303 return boList; 304 } 305 } 306 criteria.put("accountsPayablePurchasingDocumentLinkIdentifier", accountsPayablePurchasingDocumentLinkIdentifier); 307 // retrieve in descending order of document number so that newer documents are in the front 308 List boList = (List) businessObjectService.findMatchingOrderBy(clazz, criteria, OLEPropertyConstants.DOCUMENT_NUMBER, false); 309 return boList; 310 } 311 312 /** 313 * @see org.kuali.ole.module.purap.document.service.PurapService#addBelowLineItems(org.kuali.ole.module.purap.document.PurchasingAccountsPayableDocument) 314 */ 315 @Override 316 @SuppressWarnings("unchecked") 317 public void addBelowLineItems(PurchasingAccountsPayableDocument document) { 318 LOG.debug("addBelowLineItems() started"); 319 320 String[] itemTypes = getBelowTheLineForDocument(document); 321 322 List<PurApItem> existingItems = document.getItems(); 323 324 List<PurApItem> belowTheLine = new ArrayList<PurApItem>(); 325 // needed in case they get out of sync below won't work 326 sortBelowTheLine(itemTypes, existingItems, belowTheLine); 327 328 List<String> existingItemTypes = new ArrayList<String>(); 329 for (PurApItem existingItem : existingItems) { 330 existingItemTypes.add(existingItem.getItemTypeCode()); 331 } 332 333 Class itemClass = document.getItemClass(); 334 335 for (int i = 0; i < itemTypes.length; i++) { 336 int lastFound; 337 if (!existingItemTypes.contains(itemTypes[i])) { 338 try { 339 if (i > 0) { 340 lastFound = existingItemTypes.lastIndexOf(itemTypes[i - 1]) + 1; 341 } else { 342 lastFound = existingItemTypes.size(); 343 } 344 PurApItem newItem = (PurApItem) itemClass.newInstance(); 345 newItem.setItemTypeCode(itemTypes[i]); 346 newItem.setPurapDocument(document); 347 existingItems.add(lastFound, newItem); 348 existingItemTypes.add(itemTypes[i]); 349 } catch (Exception e) { 350 // do something 351 } 352 } 353 } 354 355 document.fixItemReferences(); 356 } 357 358 /** 359 * Sorts the below the line elements 360 * 361 * @param itemTypes 362 * @param existingItems 363 * @param belowTheLine 364 */ 365 protected void sortBelowTheLine(String[] itemTypes, List<PurApItem> existingItems, List<PurApItem> belowTheLine) { 366 LOG.debug("sortBelowTheLine() started"); 367 368 // sort existing below the line if any 369 for (int i = 0; i < existingItems.size(); i++) { 370 PurApItem purApItem = existingItems.get(i); 371 if (purApItem.getItemType().isAdditionalChargeIndicator()) { 372 belowTheLine.add(existingItems.get(i)); 373 } 374 } 375 existingItems.removeAll(belowTheLine); 376 for (String itemType : itemTypes) { 377 for (PurApItem purApItem : belowTheLine) { 378 if (StringUtils.equalsIgnoreCase(purApItem.getItemTypeCode(), itemType)) { 379 existingItems.add(purApItem); 380 break; 381 } 382 } 383 } 384 belowTheLine.removeAll(existingItems); 385 /*if (belowTheLine.size() != 0) { 386 throw new RuntimeException("below the line item sort didn't work: trying to remove an item without adding it back"); 387 }*/ 388 } 389 390 /** 391 * @see org.kuali.ole.module.purap.document.service.PurapService#sortBelowTheLine(String[], java.util.List, java.util.List) 392 */ 393 @Override 394 public void sortBelowTheLine(PurchasingAccountsPayableDocument document) { 395 LOG.debug("sortBelowTheLine() started"); 396 397 String[] itemTypes = getBelowTheLineForDocument(document); 398 399 List<PurApItem> existingItems = document.getItems(); 400 401 List<PurApItem> belowTheLine = new ArrayList<PurApItem>(); 402 // needed in case they get out of sync below won't work 403 sortBelowTheLine(itemTypes, existingItems, belowTheLine); 404 } 405 406 /** 407 * @see org.kuali.ole.module.purap.document.service.PurapService#getBelowTheLineForDocument(org.kuali.ole.module.purap.document.PurchasingAccountsPayableDocument) 408 */ 409 @Override 410 public String[] getBelowTheLineForDocument(PurchasingAccountsPayableDocument document) { 411 LOG.debug("getBelowTheLineForDocument() started"); 412 413 // Obtain a list of below the line items from system parameter 414// String documentTypeClassName = document.getClass().getName(); 415// String[] documentTypeArray = StringUtils.split(documentTypeClassName, "."); 416// String documentType = documentTypeArray[documentTypeArray.length - 1]; 417 418 //FIXME RELEASE 3 (hjs) why is this "if" here with no code in it? is it supposed to be doing somethign? 419 // If it's a credit memo, we'll have to append the source of the credit memo 420 // whether it's created from a Vendor, a PO or a PREQ. 421// if (documentType.equals("CreditMemoDocument")) { 422// 423// } 424 425 String documentType = dataDictionaryService.getDocumentTypeNameByClass(document.getClass()); 426 427 try { 428 return parameterService.getParameterValuesAsString(Class.forName(PurapConstants.PURAP_DETAIL_TYPE_CODE_MAP.get(documentType)), PurapConstants.BELOW_THE_LINES_PARAMETER).toArray(new String[]{}); 429 } catch (ClassNotFoundException e) { 430 throw new RuntimeException("The getBelowTheLineForDocument method of PurapServiceImpl was unable to resolve the document class for type: " + PurapConstants.PURAP_DETAIL_TYPE_CODE_MAP.get(documentType), e); 431 } 432 } 433 434 /** 435 * @see org.kuali.ole.module.purap.document.service.PurapService#getBelowTheLineByType(org.kuali.ole.module.purap.document.PurchasingAccountsPayableDocument, 436 * org.kuali.ole.module.purap.businessobject.ItemType) 437 */ 438 @Override 439 public PurApItem getBelowTheLineByType(PurchasingAccountsPayableDocument document, ItemType iT) { 440 LOG.debug("getBelowTheLineByType() started"); 441 442 String[] itemTypes = getBelowTheLineForDocument(document); 443 boolean foundItemType = false; 444 for (String itemType : itemTypes) { 445 if (StringUtils.equals(iT.getItemTypeCode(), itemType)) { 446 foundItemType = true; 447 break; 448 } 449 } 450 if (!foundItemType) { 451 return null; 452 } 453 454 PurApItem belowTheLineItem = null; 455 for (PurApItem item : document.getItems()) { 456 if (item.getItemType().isAdditionalChargeIndicator()) { 457 if (StringUtils.equals(iT.getItemTypeCode(), item.getItemType().getItemTypeCode())) { 458 belowTheLineItem = item; 459 break; 460 } 461 } 462 } 463 return belowTheLineItem; 464 } 465 466 /** 467 * @see org.kuali.ole.module.purap.document.service.PurapService#getDateFromOffsetFromToday(int) 468 */ 469 @Override 470 public Date getDateFromOffsetFromToday(int offsetDays) { 471 Calendar calendar = dateTimeService.getCurrentCalendar(); 472 calendar.add(Calendar.DATE, offsetDays); 473 return new Date(calendar.getTimeInMillis()); 474 } 475 476 /** 477 * @see org.kuali.ole.module.purap.document.service.PurapService#isDateInPast(java.sql.Date) 478 */ 479 @Override 480 public boolean isDateInPast(Date compareDate) { 481 LOG.debug("isDateInPast() started"); 482 483 Date today = dateTimeService.getCurrentSqlDate(); 484 int diffFromToday = dateTimeService.dateDiff(today, compareDate, false); 485 return (diffFromToday < 0); 486 } 487 488 /** 489 * @see org.kuali.ole.module.purap.document.service.PurapService#isDateMoreThanANumberOfDaysAway(java.sql.Date, int) 490 */ 491 @Override 492 public boolean isDateMoreThanANumberOfDaysAway(Date compareDate, int daysAway) { 493 LOG.debug("isDateMoreThanANumberOfDaysAway() started"); 494 495 Date todayAtMidnight = dateTimeService.getCurrentSqlDateMidnight(); 496 Calendar daysAwayCalendar = dateTimeService.getCalendar(todayAtMidnight); 497 daysAwayCalendar.add(Calendar.DATE, daysAway); 498 Timestamp daysAwayTime = new Timestamp(daysAwayCalendar.getTime().getTime()); 499 Calendar compareCalendar = dateTimeService.getCalendar(compareDate); 500 compareCalendar.set(Calendar.HOUR, 0); 501 compareCalendar.set(Calendar.MINUTE, 0); 502 compareCalendar.set(Calendar.SECOND, 0); 503 compareCalendar.set(Calendar.MILLISECOND, 0); 504 compareCalendar.set(Calendar.AM_PM, Calendar.AM); 505 Timestamp compareTime = new Timestamp(compareCalendar.getTime().getTime()); 506 return (compareTime.compareTo(daysAwayTime) > 0); 507 } 508 509 /** 510 * @see org.kuali.ole.module.purap.document.service.PurapService#isDateAYearAfterToday(java.sql.Date) 511 */ 512 @Override 513 public boolean isDateAYearBeforeToday(Date compareDate) { 514 LOG.debug("isDateAYearBeforeToday() started"); 515 516 Calendar calendar = dateTimeService.getCurrentCalendar(); 517 calendar.add(Calendar.YEAR, -1); 518 Date yearAgo = new Date(calendar.getTimeInMillis()); 519 int diffFromYearAgo = dateTimeService.dateDiff(compareDate, yearAgo, false); 520 return (diffFromYearAgo > 0); 521 } 522 523 /** 524 * @see org.kuali.ole.module.purap.document.service.PurapService#getApoLimit(Integer, String, String) 525 */ 526 @Override 527 @SuppressWarnings("unchecked") 528 public KualiDecimal getApoLimit(Integer vendorContractGeneratedIdentifier, String chart, String org) { 529 LOG.debug("getApoLimit() started"); 530 531 KualiDecimal purchaseOrderTotalLimit = vendorService.getApoLimitFromContract(vendorContractGeneratedIdentifier, chart, org); 532 533 // We didn't find the limit on the vendor contract, get it from the org parameter table. 534 if (ObjectUtils.isNull(purchaseOrderTotalLimit) && !ObjectUtils.isNull(chart) && !ObjectUtils.isNull(org)) { 535 OrganizationParameter organizationParameter = new OrganizationParameter(); 536 organizationParameter.setChartOfAccountsCode(chart); 537 organizationParameter.setOrganizationCode(org); 538 Map orgParamKeys = persistenceService.getPrimaryKeyFieldValues(organizationParameter); 539 orgParamKeys.put(KRADPropertyConstants.ACTIVE_INDICATOR, true); 540 organizationParameter = businessObjectService.findByPrimaryKey(OrganizationParameter.class, orgParamKeys); 541 purchaseOrderTotalLimit = (organizationParameter == null) ? null : organizationParameter.getOrganizationAutomaticPurchaseOrderLimit(); 542 } 543 544 if (ObjectUtils.isNull(purchaseOrderTotalLimit)) { 545 String defaultLimit = parameterService.getParameterValueAsString(RequisitionDocument.class, PurapParameterConstants.AUTOMATIC_PURCHASE_ORDER_DEFAULT_LIMIT_AMOUNT); 546 purchaseOrderTotalLimit = new KualiDecimal(defaultLimit); 547 } 548 549 return purchaseOrderTotalLimit; 550 } 551 552 /** 553 * @see org.kuali.ole.module.purap.document.service.PurapService#isFullDocumentEntryCompleted(org.kuali.ole.module.purap.document.PurchasingAccountsPayableDocument) 554 */ 555 @Override 556 public boolean isFullDocumentEntryCompleted(PurchasingAccountsPayableDocument purapDocument) { 557 LOG.debug("isFullDocumentEntryCompleted() started"); 558 559 // for now just return true if not in one of the first few states 560 boolean value = false; 561 if (purapDocument instanceof PaymentRequestDocument) { 562 value = PurapConstants.PaymentRequestStatuses.STATUS_ORDER.isFullDocumentEntryCompleted(purapDocument.getApplicationDocumentStatus()); 563 } else if (purapDocument instanceof VendorCreditMemoDocument) { 564 value = PurapConstants.CreditMemoStatuses.STATUS_ORDER.isFullDocumentEntryCompleted(purapDocument.getApplicationDocumentStatus()); 565 } else if (purapDocument instanceof OleInvoiceDocument) { 566 value = PurapConstants.InvoiceStatuses.STATUS_ORDER.isFullDocumentEntryCompleted(purapDocument.getApplicationDocumentStatus()); 567 } 568 return value; 569 } 570 571 /** 572 * @see org.kuali.ole.module.purap.document.service.PurapService#isPaymentRequestFullDocumentEntryCompleted(String) 573 */ 574 @Override 575 public boolean isPaymentRequestFullDocumentEntryCompleted(String purapDocumentStatus) { 576 LOG.debug("isPaymentRequestFullDocumentEntryCompleted() started"); 577 return PurapConstants.PaymentRequestStatuses.STATUS_ORDER.isFullDocumentEntryCompleted(purapDocumentStatus); 578 } 579 580 /** 581 * @see org.kuali.ole.module.purap.document.service.PurapService#isVendorCreditMemoFullDocumentEntryCompleted(String) 582 */ 583 @Override 584 public boolean isVendorCreditMemoFullDocumentEntryCompleted(String purapDocumentStatus) { 585 LOG.debug("isVendorCreditMemoFullDocumentEntryCompleted() started"); 586 return PurapConstants.CreditMemoStatuses.STATUS_ORDER.isFullDocumentEntryCompleted(purapDocumentStatus); 587 } 588 589 /** 590 * @see org.kuali.ole.module.purap.document.service.PurapService#isInvoiceFullDocumentEntryCompleted(String) 591 */ 592 @Override 593 public boolean isInvoiceFullDocumentEntryCompleted(String purapDocumentStatus) { 594 LOG.debug("isInvoiceFullDocumentEntryCompleted() started"); 595 return PurapConstants.InvoiceStatuses.STATUS_ORDER.isFullDocumentEntryCompleted(purapDocumentStatus); 596 } 597 598 /** 599 * Main hook point for close/Reopen PO. 600 * 601 * @see org.kuali.ole.module.purap.document.service.PurapService#performLogicForCloseReopenPO(org.kuali.ole.module.purap.document.PurchasingAccountsPayableDocument) 602 */ 603 @Override 604 public void performLogicForCloseReopenPO(PurchasingAccountsPayableDocument purapDocument) { 605 LOG.debug("performLogicForCloseReopenPO() started"); 606 607 if (purapDocument instanceof PaymentRequestDocument) { 608 PaymentRequestDocument paymentRequest = (PaymentRequestDocument) purapDocument; 609 610 if (paymentRequest.isClosePurchaseOrderIndicator() && PurchaseOrderStatuses.APPDOC_OPEN.equals(paymentRequest.getPurchaseOrderDocument().getApplicationDocumentStatus())) { 611 // get the po id and get the current po 612 // check the current po: if status is not closed and there is no pending action... route close po as system user 613 processCloseReopenPo((AccountsPayableDocumentBase) purapDocument, PurapConstants.PurchaseOrderDocTypes.PURCHASE_ORDER_CLOSE_DOCUMENT); 614 } 615 616 } else if (purapDocument instanceof VendorCreditMemoDocument) { 617 VendorCreditMemoDocument creditMemo = (VendorCreditMemoDocument) purapDocument; 618 619 if (creditMemo.isReopenPurchaseOrderIndicator() && PurchaseOrderStatuses.APPDOC_CLOSED.equals(creditMemo.getPurchaseOrderDocument().getApplicationDocumentStatus())) { 620 // get the po id and get the current PO 621 // route 'Re-Open PO Document' if PO criteria meets requirements from business rules 622 processCloseReopenPo((AccountsPayableDocumentBase) purapDocument, PurapConstants.PurchaseOrderDocTypes.PURCHASE_ORDER_REOPEN_DOCUMENT); 623 } 624 625 } else { 626 throw new RuntimeException("Attempted to perform full entry logic for unhandled document type '" + purapDocument.getClass().getName() + "'"); 627 } 628 629 } 630 631 /** 632 * Remove items that have not been "entered" which means no data has been added to them so no more processing needs to continue 633 * on these items. 634 * 635 * @param apDocument AccountsPayableDocument which contains list of items to be reviewed 636 */ 637 @Override 638 public void deleteUnenteredItems(PurapItemOperations document) { 639 LOG.debug("deleteUnenteredItems() started"); 640 641 List<PurapEnterableItem> deletionList = new ArrayList<PurapEnterableItem>(); 642 for (PurapEnterableItem item : (List<PurapEnterableItem>) document.getItems()) { 643 if (!item.isConsideredEntered()) { 644 deletionList.add(item); 645 } 646 } 647 document.getItems().removeAll(deletionList); 648 } 649 650 /** 651 * Actual method that will close or reopen a po. 652 * 653 * @param apDocument AccountsPayableDocument 654 * @param docType 655 */ 656 @SuppressWarnings("unchecked") 657 public void processCloseReopenPo(AccountsPayableDocumentBase apDocument, String docType) { 658 LOG.debug("processCloseReopenPo() started"); 659 660 String action = null; 661 String newStatus = null; 662 // setup text for note that will be created, will either be closed or reopened 663 if (PurapConstants.PurchaseOrderDocTypes.PURCHASE_ORDER_CLOSE_DOCUMENT.equals(docType)) { 664 action = "closed"; 665 newStatus = PurchaseOrderStatuses.APPDOC_PENDING_CLOSE; 666 } else if (PurapConstants.PurchaseOrderDocTypes.PURCHASE_ORDER_REOPEN_DOCUMENT.equals(docType)) { 667 action = "reopened"; 668 newStatus = PurchaseOrderStatuses.APPDOC_PENDING_REOPEN; 669 } else { 670 String errorMessage = "Method processCloseReopenPo called using ID + '" + apDocument.getPurapDocumentIdentifier() + "' and invalid doc type '" + docType + "'"; 671 LOG.error(errorMessage); 672 throw new RuntimeException(errorMessage); 673 } 674 675 676 Integer poId = apDocument.getPurchaseOrderIdentifier(); 677 PurchaseOrderDocument purchaseOrderDocument = purchaseOrderService.getCurrentPurchaseOrder(poId); 678 if (!StringUtils.equalsIgnoreCase(purchaseOrderDocument.getDocumentHeader().getWorkflowDocument().getDocumentTypeName(), docType)) { 679 // we are skipping the validation above because it would be too late to correct any errors (i.e. because in 680 // post-processing) 681 purchaseOrderService.createAndRoutePotentialChangeDocument(purchaseOrderDocument.getDocumentNumber(), docType, assemblePurchaseOrderNote(apDocument, docType, action), new ArrayList(), newStatus); 682 } 683 684 /* 685 * if we made it here, route document has not errored out, so set appropriate indicator depending on what is being 686 * requested. 687 */ 688 if (PurapConstants.PurchaseOrderDocTypes.PURCHASE_ORDER_CLOSE_DOCUMENT.equals(docType)) { 689 apDocument.setClosePurchaseOrderIndicator(false); 690 691 //add a note to the purchase order indicating it has been closed by a payment request document 692 String userName = apDocument.getLastActionPerformedByPersonName(); 693 StringBuffer poNote = new StringBuffer(""); 694 poNote.append("PO was closed manually by "); 695 poNote.append(userName); 696 poNote.append(" in approving PREQ with ID "); 697 poNote.append(apDocument.getDocumentNumber()); 698 699 //save the note to the purchase order 700 try { 701 Note noteObj = documentService.createNoteFromDocument(apDocument.getPurchaseOrderDocument(), poNote.toString()); 702 noteObj.setNoteTypeCode(apDocument.getPurchaseOrderDocument().getNoteType().getCode()); 703 apDocument.getPurchaseOrderDocument().addNote(noteObj); 704 noteService.save(noteObj); 705 } catch (Exception e) { 706 String errorMessage = "Error creating and saving close note for purchase order with document service"; 707 LOG.error("processCloseReopenPo() " + errorMessage, e); 708 throw new RuntimeException(errorMessage, e); 709 } 710 } else if (PurapConstants.PurchaseOrderDocTypes.PURCHASE_ORDER_REOPEN_DOCUMENT.equals(docType)) { 711 apDocument.setReopenPurchaseOrderIndicator(false); 712 } 713 714 } 715 716 /** 717 * Generate a note for the close/reopen po method. 718 * 719 * @param docType 720 * @param preqId 721 * @return Note to be saved 722 */ 723 protected String assemblePurchaseOrderNote(AccountsPayableDocumentBase apDocument, String docType, String action) { 724 LOG.debug("assemblePurchaseOrderNote() started"); 725 726 String documentLabel = dataDictionaryService.getDocumentLabelByClass(apDocument.getClass()); 727 StringBuffer closeReopenNote = new StringBuffer(""); 728 String userName = GlobalVariables.getUserSession().getPerson().getName(); 729 closeReopenNote.append(dataDictionaryService.getDocumentLabelByTypeName(OLEConstants.FinancialDocumentTypeCodes.PURCHASE_ORDER)); 730 closeReopenNote.append(" will be manually "); 731 closeReopenNote.append(action); 732 closeReopenNote.append(" by "); 733 closeReopenNote.append(userName); 734 closeReopenNote.append(" when approving "); 735 closeReopenNote.append(documentLabel); 736 closeReopenNote.append(" with "); 737 closeReopenNote.append(dataDictionaryService.getAttributeLabel(apDocument.getClass(), PurapPropertyConstants.PURAP_DOC_ID)); 738 closeReopenNote.append(" "); 739 closeReopenNote.append(apDocument.getPurapDocumentIdentifier()); 740 741 return closeReopenNote.toString(); 742 } 743 744 /** 745 * @see org.kuali.ole.module.purap.document.service.PurapService#performLogicWithFakedUserSession(String, org.kuali.ole.module.purap.document.service.LogicContainer, Object[]) 746 */ 747 @Override 748 public Object performLogicWithFakedUserSession(String requiredPersonPersonUserId, LogicContainer logicToRun, Object... objects) throws WorkflowException, Exception { 749 LOG.debug("performLogicWithFakedUserSession() started"); 750 751 if (StringUtils.isBlank(requiredPersonPersonUserId)) { 752 throw new RuntimeException("Attempted to perform logic with a fake user session with a blank user person id: '" + requiredPersonPersonUserId + "'"); 753 } 754 if (ObjectUtils.isNull(logicToRun)) { 755 throw new RuntimeException("Attempted to perform logic with a fake user session with no logic to run"); 756 } 757 UserSession actualUserSession = GlobalVariables.getUserSession(); 758 try { 759 GlobalVariables.setUserSession(new UserSession(requiredPersonPersonUserId)); 760 return logicToRun.runLogic(objects); 761 } finally { 762 GlobalVariables.setUserSession(actualUserSession); 763 } 764 } 765 766 /** 767 * @see org.kuali.ole.module.purap.document.service.PurchaseOrderService#saveDocumentNoValidation(org.kuali.ole.module.purap.document.PurchaseOrderDocument) 768 */ 769 @Override 770 public void saveDocumentNoValidation(Document document) { 771 try { 772 // FIXME The following code of refreshing document header is a temporary fix for the issue that 773 // in some cases (seem random) the doc header fields are null; and if doc header is refreshed, the workflow doc becomes null. 774 // The root cause of this is that when some docs are retrieved manually using OJB criteria, ref objs such as doc header or workflow doc 775 // aren't retrieved; the solution would be to add these refreshing when documents are retrieved in those OJB methods. 776 if(document != null) { 777 if (document.getDocumentHeader() != null && document.getDocumentHeader().getDocumentNumber() == null) { 778 WorkflowDocument workflowDocument = document.getDocumentHeader().getWorkflowDocument(); 779 document.refreshReferenceObject("documentHeader"); 780 document.getDocumentHeader().setWorkflowDocument(workflowDocument); 781 } 782 documentService.saveDocument(document, DocumentSystemSaveEvent.class); 783 784 // At this point, the work-flow status will not change for the current document, but the document status will. 785 // This causes the search indices for the document to become out of synch, and will show a different status type 786 // in the RICE lookup results screen. 787 final DocumentAttributeIndexingQueue documentAttributeIndexingQueue = KewApiServiceLocator.getDocumentAttributeIndexingQueue(); 788 789 documentAttributeIndexingQueue.indexDocument(document.getDocumentNumber()); 790 } 791 } catch (WorkflowException we) { 792 String errorMsg = "Workflow error saving document # " + document.getDocumentHeader().getDocumentNumber() + " " + we.getMessage(); 793 LOG.error(errorMsg, we); 794 throw new RuntimeException(errorMsg, we); 795 } catch (NumberFormatException ne) { 796 String errorMsg = "Invalid document number format for document # " + document.getDocumentHeader().getDocumentNumber() + " " + ne.getMessage(); 797 LOG.error(errorMsg, ne); 798 throw new RuntimeException(errorMsg, ne); 799 } 800 } 801 802 /** 803 * @see org.kuali.ole.module.purap.document.service.PurapService#isDocumentStoppedInRouteNode(org.kuali.ole.module.purap.document.PurchasingAccountsPayableDocument, String) 804 */ 805 @Override 806 public boolean isDocumentStoppedInRouteNode(PurchasingAccountsPayableDocument document, String nodeName) { 807 WorkflowDocument workflowDoc = document.getDocumentHeader().getWorkflowDocument(); 808 Set<String> currentRouteLevels = workflowDoc.getCurrentNodeNames(); 809 if (currentRouteLevels.contains(nodeName) && workflowDoc.isApprovalRequested()) { 810 return true; 811 } 812 return false; 813 } 814 815 /** 816 * @see org.kuali.ole.module.purap.document.service.PurapService#allowEncumberNextFiscalYear() 817 */ 818 @Override 819 public boolean allowEncumberNextFiscalYear() { 820 LOG.debug("allowEncumberNextFiscalYear() started"); 821 822 java.util.Date today = dateTimeService.getCurrentDate(); 823 java.util.Date closingDate = universityDateService.getLastDateOfFiscalYear(universityDateService.getCurrentFiscalYear()); 824 int allowEncumberNext = (Integer.parseInt(parameterService.getParameterValueAsString(RequisitionDocument.class, PurapRuleConstants.ALLOW_ENCUMBER_NEXT_YEAR_DAYS))); 825 int diffTodayClosing = dateTimeService.dateDiff(today, closingDate, false); 826 827 if (ObjectUtils.isNotNull(closingDate) && ObjectUtils.isNotNull(today) && ObjectUtils.isNotNull(allowEncumberNext)) { 828 if (LOG.isDebugEnabled()) { 829 LOG.debug("allowEncumberNextFiscalYear() today = " + dateTimeService.toDateString(today) + "; encumber next FY range = " + allowEncumberNext + " - " + dateTimeService.toDateTimeString(today)); 830 } 831 832 if (allowEncumberNext >= diffTodayClosing && diffTodayClosing >= KualiDecimal.ZERO.intValue()) { 833 LOG.debug("allowEncumberNextFiscalYear() encumber next FY allowed; return true."); 834 return true; 835 } 836 } 837 LOG.debug("allowEncumberNextFiscalYear() encumber next FY not allowed; return false."); 838 return false; 839 } 840 841 /** 842 * @see org.kuali.ole.module.purap.document.service.PurapService#getAllowedFiscalYears() 843 */ 844 @Override 845 public List<Integer> getAllowedFiscalYears() { 846 List<Integer> allowedYears = new ArrayList<Integer>(); 847 Integer currentFY = universityDateService.getCurrentFiscalYear(); 848 allowedYears.add(currentFY); 849 if (allowEncumberNextFiscalYear()) { 850 allowedYears.add(currentFY + 1); 851 } 852 return allowedYears; 853 } 854 855 /** 856 * @see org.kuali.ole.module.purap.document.service.PurapService#isTodayWithinApoAllowedRange() 857 */ 858 @Override 859 public boolean isTodayWithinApoAllowedRange() { 860 java.util.Date today = dateTimeService.getCurrentDate(); 861 Integer currentFY = universityDateService.getCurrentFiscalYear(); 862 java.util.Date closingDate = universityDateService.getLastDateOfFiscalYear(currentFY); 863 int allowApoDate = (Integer.parseInt(parameterService.getParameterValueAsString(RequisitionDocument.class, PurapRuleConstants.ALLOW_APO_NEXT_FY_DAYS))); 864 int diffTodayClosing = dateTimeService.dateDiff(today, closingDate, true); 865 866 return diffTodayClosing <= allowApoDate; 867 } 868 869 /** 870 * @see org.kuali.ole.module.purap.document.service.PurapService#clearTax(org.kuali.ole.module.purap.document.PurchasingAccountsPayableDocument) 871 */ 872 @Override 873 public void clearTax(PurchasingAccountsPayableDocument purapDocument, boolean useTax) { 874 for (PurApItem item : purapDocument.getItems()) { 875 if (useTax) { 876 item.getUseTaxItems().clear(); 877 } else { 878 item.setItemTaxAmount(null); 879 } 880 } 881 } 882 883 @Override 884 public void updateUseTaxIndicator(PurchasingAccountsPayableDocument purapDocument, boolean newUseTaxIndicatorValue) { 885 boolean currentUseTaxIndicator = purapDocument.isUseTaxIndicator(); 886 if (currentUseTaxIndicator != newUseTaxIndicatorValue) { 887 //i.e. if the indicator changed clear out the tax 888 clearTax(purapDocument, currentUseTaxIndicator); 889 } 890 purapDocument.setUseTaxIndicator(newUseTaxIndicatorValue); 891 } 892 893 /** 894 * @see org.kuali.ole.module.purap.document.service.PurapService#calculateTax(org.kuali.ole.module.purap.document.PurchasingAccountsPayableDocument) 895 */ 896 @Override 897 public void calculateTax(PurchasingAccountsPayableDocument purapDocument) { 898 899 boolean salesTaxInd = SpringContext.getBean(ParameterService.class).getParameterValueAsBoolean(OleParameterConstants.PURCHASING_DOCUMENT.class, PurapParameterConstants.ENABLE_SALES_TAX_IND); 900 boolean useTaxIndicator = purapDocument.isUseTaxIndicator(); 901 String deliveryState = getDeliveryState(purapDocument); 902 String deliveryPostalCode = getDeliveryPostalCode(purapDocument); 903 Date transactionTaxDate = purapDocument.getTransactionTaxDate(); 904 905 //calculate if sales tax enabled for purap 906 if (salesTaxInd || useTaxIndicator) { 907 //iterate over items and calculate tax if taxable 908 for (PurApItem item : purapDocument.getItems()) { 909 if (isTaxable(useTaxIndicator, deliveryState, item)) { 910 calculateItemTax(useTaxIndicator, deliveryPostalCode, transactionTaxDate, item, item.getUseTaxClass(), purapDocument); 911 } 912 } 913 } 914 } 915 916 @Override 917 public String getDeliveryState(PurchasingAccountsPayableDocument purapDocument) { 918 if (purapDocument instanceof PurchasingDocument) { 919 PurchasingDocument document = (PurchasingDocument) purapDocument; 920 return document.getDeliveryStateCode(); 921 } else if (purapDocument instanceof AccountsPayableDocument) { 922 AccountsPayableDocument document = (AccountsPayableDocument) purapDocument; 923 if (document.getPurchaseOrderDocument() == null) { 924 throw new RuntimeException("PurchaseOrder document does not exists"); 925 } 926 return document.getPurchaseOrderDocument().getDeliveryStateCode(); 927 } 928 return null; 929 } 930 931 protected String getDeliveryPostalCode(PurchasingAccountsPayableDocument purapDocument) { 932 if (purapDocument instanceof PurchasingDocument) { 933 PurchasingDocument document = (PurchasingDocument) purapDocument; 934 return document.getDeliveryPostalCode(); 935 } else if (purapDocument instanceof AccountsPayableDocument) { 936 AccountsPayableDocument docBase = (AccountsPayableDocument) purapDocument; 937 if (docBase.getPurchaseOrderDocument() == null) { 938 throw new RuntimeException("PurchaseOrder document does not exists"); 939 } 940 return docBase.getPurchaseOrderDocument().getDeliveryPostalCode(); 941 } 942 return null; 943 } 944 945 /** 946 * Determines if the item is taxable based on a decision tree. 947 * 948 * @param useTaxIndicator 949 * @param deliveryState 950 * @param item 951 * @return 952 */ 953 @Override 954 public boolean isTaxable(boolean useTaxIndicator, String deliveryState, PurApItem item) { 955 956 boolean taxable = false; 957 958 if (item.getItemType().isTaxableIndicator() && 959 ((ObjectUtils.isNull(item.getItemTaxAmount()) && useTaxIndicator == false) || useTaxIndicator) && 960 (doesCommodityAllowCallToTaxService(item)) && 961 (doesAccountAllowCallToTaxService(deliveryState, item))) { 962 963 taxable = true; 964 } 965 return taxable; 966 } 967 968 /** 969 * @see org.kuali.ole.module.purap.document.service.PurapService#isTaxableForSummary(boolean, String, org.kuali.ole.module.purap.businessobject.PurApItem) 970 */ 971 @Override 972 public boolean isTaxableForSummary(boolean useTaxIndicator, String deliveryState, PurApItem item) { 973 974 boolean taxable = false; 975 976 if (item.getItemType().isTaxableIndicator() && 977 (doesCommodityAllowCallToTaxService(item)) && 978 (doesAccountAllowCallToTaxService(deliveryState, item))) { 979 980 taxable = true; 981 } 982 return taxable; 983 } 984 985 /** 986 * Determines if the the tax service should be called due to the commodity code. 987 * 988 * @param item 989 * @return 990 */ 991 protected boolean doesCommodityAllowCallToTaxService(PurApItem item) { 992 boolean callService = true; 993 994 // only check for commodity code on above the line times (additional charges don't allow commodity code) 995 if (item.getItemType().isLineItemIndicator()) { 996 if (item instanceof PurchasingItem) { 997 PurchasingItemBase purItem = (PurchasingItemBase) item; 998 callService = isCommodityCodeTaxable(purItem.getCommodityCode()); 999 }// if not a purchasing item, then pull item from PO 1000 else if (item instanceof AccountsPayableItem) { 1001 AccountsPayableItem apItem = (AccountsPayableItem) item; 1002 PurchaseOrderItem poItem = apItem.getPurchaseOrderItem(); 1003 if (ObjectUtils.isNotNull(poItem)) { 1004 callService = isCommodityCodeTaxable(poItem.getCommodityCode()); 1005 } 1006 } 1007 } 1008 1009 return callService; 1010 } 1011 1012 protected boolean isCommodityCodeTaxable(CommodityCode commodityCode) { 1013 boolean isTaxable = true; 1014 1015 if (ObjectUtils.isNotNull(commodityCode)) { 1016 1017 if (commodityCode.isSalesTaxIndicator() == false) { 1018 //not taxable, so don't call service 1019 isTaxable = false; 1020 }//if true we want to call service 1021 1022 }//if null, return true 1023 1024 return isTaxable; 1025 } 1026 1027 /** 1028 * @see org.kuali.ole.module.purap.document.service.PurapService#isDeliveryStateTaxable(String) 1029 */ 1030 @Override 1031 public boolean isDeliveryStateTaxable(String deliveryState) { 1032 ParameterEvaluator parmEval = /*REFACTORME*/SpringContext.getBean(ParameterEvaluatorService.class).getParameterEvaluator(OleParameterConstants.PURCHASING_DOCUMENT.class, TaxParameters.TAXABLE_DELIVERY_STATES, deliveryState); 1033 return parmEval.evaluationSucceeds(); 1034 } 1035 1036 /** 1037 * Checks if the account is taxable, based on the delivery state, fund/subfund groups, and object code level/consolidations. 1038 * 1039 * @param deliveryState 1040 * @param item 1041 * @return 1042 */ 1043 protected boolean doesAccountAllowCallToTaxService(String deliveryState, PurApItem item) { 1044 boolean callService = false; 1045 boolean deliveryStateTaxable = isDeliveryStateTaxable(deliveryState); 1046 1047 for (PurApAccountingLine acctLine : item.getSourceAccountingLines()) { 1048 if (isAccountingLineTaxable(acctLine, deliveryStateTaxable)) { 1049 callService = true; 1050 break; 1051 } 1052 } 1053 1054 return callService; 1055 } 1056 1057 /** 1058 * @see org.kuali.ole.module.purap.document.service.PurapService#isAccountingLineTaxable(org.kuali.ole.module.purap.businessobject.PurApAccountingLine, boolean) 1059 */ 1060 @Override 1061 public boolean isAccountingLineTaxable(PurApAccountingLine acctLine, boolean deliveryStateTaxable) { 1062 boolean isTaxable = false; 1063 String parameterSuffix = null; 1064 1065 if (deliveryStateTaxable) { 1066 parameterSuffix = TaxParameters.FOR_TAXABLE_STATES_SUFFIX; 1067 } else { 1068 parameterSuffix = TaxParameters.FOR_NON_TAXABLE_STATES_SUFFIX; 1069 } 1070 1071 // is account (fund/subfund) and object code (level/consolidation) taxable? 1072 if (isAccountTaxable(parameterSuffix, acctLine) && isObjectCodeTaxable(parameterSuffix, acctLine)) { 1073 isTaxable = true; 1074 } 1075 1076 return isTaxable; 1077 } 1078 1079 /** 1080 * Checks if the account fund/subfund groups are in a set of parameters taking into account allowed/denied constraints and 1081 * ultimately determines if taxable. 1082 * 1083 * @param parameterSuffix 1084 * @param acctLine 1085 * @return 1086 */ 1087 protected boolean isAccountTaxable(String parameterSuffix, PurApAccountingLine acctLine) { 1088 1089 boolean isAccountTaxable = false; 1090 String fundParam = TaxParameters.TAXABLE_FUND_GROUPS_PREFIX + parameterSuffix; 1091 String subFundParam = TaxParameters.TAXABLE_SUB_FUND_GROUPS_PREFIX + parameterSuffix; 1092 ParameterEvaluator fundParamEval = null; 1093 ParameterEvaluator subFundParamEval = null; 1094 1095 if (ObjectUtils.isNull(acctLine.getAccount().getSubFundGroup())) { 1096 acctLine.refreshNonUpdateableReferences(); 1097 } 1098 1099 fundParamEval = SpringContext.getBean(ParameterEvaluatorService.class).getParameterEvaluator(OleParameterConstants.PURCHASING_DOCUMENT.class, fundParam, acctLine.getAccount().getSubFundGroup().getFundGroupCode()); 1100 subFundParamEval = SpringContext.getBean(ParameterEvaluatorService.class).getParameterEvaluator(OleParameterConstants.PURCHASING_DOCUMENT.class, subFundParam, acctLine.getAccount().getSubFundGroupCode()); 1101 1102 if ((isAllowedFound(fundParamEval) && (isAllowedFound(subFundParamEval) || isAllowedNotFound(subFundParamEval) || isDeniedNotFound(subFundParamEval))) || 1103 (isAllowedNotFound(fundParamEval) && isAllowedFound(subFundParamEval)) || 1104 (isDeniedFound(fundParamEval) && isAllowedFound(subFundParamEval)) || 1105 (isDeniedNotFound(fundParamEval) && (isAllowedFound(subFundParamEval) || isAllowedNotFound(subFundParamEval) || isDeniedNotFound(subFundParamEval)))) { 1106 1107 isAccountTaxable = true; 1108 } 1109 1110 return isAccountTaxable; 1111 } 1112 1113 /** 1114 * Checks if the object code level/consolidation groups are in a set of parameters taking into account allowed/denied constraints and 1115 * ultimately determines if taxable. 1116 * 1117 * @param parameterSuffix 1118 * @param acctLine 1119 * @return 1120 */ 1121 protected boolean isObjectCodeTaxable(String parameterSuffix, PurApAccountingLine acctLine) { 1122 1123 boolean isObjectCodeTaxable = false; 1124 String levelParam = TaxParameters.TAXABLE_OBJECT_LEVELS_PREFIX + parameterSuffix; 1125 String consolidationParam = TaxParameters.TAXABLE_OBJECT_CONSOLIDATIONS_PREFIX + parameterSuffix; 1126 ParameterEvaluator levelParamEval = null; 1127 ParameterEvaluator consolidationParamEval = null; 1128 1129 //refresh financial object level 1130 acctLine.getObjectCode().refreshReferenceObject("financialObjectLevel"); 1131 1132 levelParamEval = SpringContext.getBean(ParameterEvaluatorService.class).getParameterEvaluator(OleParameterConstants.PURCHASING_DOCUMENT.class, levelParam, acctLine.getObjectCode().getFinancialObjectLevelCode()); 1133 consolidationParamEval = SpringContext.getBean(ParameterEvaluatorService.class).getParameterEvaluator(OleParameterConstants.PURCHASING_DOCUMENT.class, consolidationParam, acctLine.getObjectCode().getFinancialObjectLevel().getFinancialConsolidationObjectCode()); 1134 1135 if ((isAllowedFound(levelParamEval) && (isAllowedFound(consolidationParamEval) || isAllowedNotFound(consolidationParamEval) || isDeniedNotFound(consolidationParamEval))) || 1136 (isAllowedNotFound(levelParamEval) && isAllowedFound(consolidationParamEval)) || 1137 (isDeniedFound(levelParamEval) && isAllowedFound(consolidationParamEval)) || 1138 (isDeniedNotFound(levelParamEval) && (isAllowedFound(consolidationParamEval) || isAllowedNotFound(consolidationParamEval) || isDeniedNotFound(consolidationParamEval)))) { 1139 1140 isObjectCodeTaxable = true; 1141 } 1142 1143 return isObjectCodeTaxable; 1144 } 1145 1146 /** 1147 * Helper method to work with parameter evaluator to find, allowed and found in parameter value. 1148 * 1149 * @param eval 1150 * @return 1151 */ 1152 protected boolean isAllowedFound(ParameterEvaluator eval) { 1153 boolean exists = false; 1154 1155 if (eval.evaluationSucceeds() && eval.constraintIsAllow()) { 1156 exists = true; 1157 } 1158 1159 return exists; 1160 } 1161 1162 /** 1163 * Helper method to work with parameter evaluator to find, allowed and not found in parameter value. 1164 * 1165 * @param eval 1166 * @return 1167 */ 1168 protected boolean isAllowedNotFound(ParameterEvaluator eval) { 1169 boolean exists = false; 1170 1171 if (eval.evaluationSucceeds() == false && eval.constraintIsAllow()) { 1172 exists = true; 1173 } 1174 1175 return exists; 1176 } 1177 1178 /** 1179 * Helper method to work with parameter evaluator to find, denied and found in parameter value. 1180 * 1181 * @param eval 1182 * @return 1183 */ 1184 protected boolean isDeniedFound(ParameterEvaluator eval) { 1185 boolean exists = false; 1186 1187 if (eval.evaluationSucceeds() == false && eval.constraintIsAllow() == false) { 1188 exists = true; 1189 } 1190 1191 return exists; 1192 } 1193 1194 /** 1195 * Helper method to work with parameter evaluator to find, denied and not found in parameter value. 1196 * 1197 * @param eval 1198 * @return 1199 */ 1200 protected boolean isDeniedNotFound(ParameterEvaluator eval) { 1201 boolean exists = false; 1202 1203 if (eval.evaluationSucceeds() && eval.constraintIsAllow() == false) { 1204 exists = true; 1205 } 1206 1207 return exists; 1208 } 1209 1210 /** 1211 * @param useTaxIndicator 1212 * @param deliveryPostalCode 1213 * @param transactionTaxDate 1214 * @param item 1215 * @param itemUseTaxClass 1216 */ 1217 @SuppressWarnings("unchecked") 1218 protected void calculateItemTax(boolean useTaxIndicator, 1219 String deliveryPostalCode, 1220 Date transactionTaxDate, 1221 PurApItem item, 1222 Class itemUseTaxClass, 1223 PurchasingAccountsPayableDocument purapDocument) { 1224 1225 if (!useTaxIndicator) { 1226 if (!StringUtils.equals(item.getItemTypeCode(), PurapConstants.ItemTypeCodes.ITEM_TYPE_PMT_TERMS_DISCOUNT_CODE) && 1227 !StringUtils.equals(item.getItemTypeCode(), PurapConstants.ItemTypeCodes.ITEM_TYPE_ORDER_DISCOUNT_CODE)) { 1228 KualiDecimal taxAmount = taxService.getTotalSalesTaxAmount(transactionTaxDate, deliveryPostalCode, item.getExtendedPrice()); 1229 item.setItemTaxAmount(taxAmount); 1230 } 1231 } else { 1232 KualiDecimal extendedPrice = item.getExtendedPrice(); 1233 1234 if (StringUtils.equals(item.getItemTypeCode(), PurapConstants.ItemTypeCodes.ITEM_TYPE_ORDER_DISCOUNT_CODE)) { 1235 KualiDecimal taxablePrice = getFullDiscountTaxablePrice(extendedPrice, purapDocument); 1236 extendedPrice = taxablePrice; 1237 } 1238 List<TaxDetail> taxDetails = taxService.getUseTaxDetails(transactionTaxDate, deliveryPostalCode, extendedPrice); 1239 List<PurApItemUseTax> newUseTaxItems = new ArrayList<PurApItemUseTax>(); 1240 if (taxDetails != null) { 1241 for (TaxDetail taxDetail : taxDetails) { 1242 try { 1243 PurApItemUseTax useTaxitem = (PurApItemUseTax) itemUseTaxClass.newInstance(); 1244 useTaxitem.setChartOfAccountsCode(taxDetail.getChartOfAccountsCode()); 1245 useTaxitem.setFinancialObjectCode(taxDetail.getFinancialObjectCode()); 1246 useTaxitem.setAccountNumber(taxDetail.getAccountNumber()); 1247 useTaxitem.setItemIdentifier(item.getItemIdentifier()); 1248 useTaxitem.setRateCode(taxDetail.getRateCode()); 1249 useTaxitem.setTaxAmount(taxDetail.getTaxAmount()); 1250 newUseTaxItems.add(useTaxitem); 1251 } catch (Exception e) { 1252 /** 1253 * Shallow.. This never happen - InstantiationException/IllegalAccessException 1254 * To be safe, throw a runtime exception 1255 */ 1256 throw new RuntimeException(e); 1257 } 1258 } 1259 } 1260 item.setUseTaxItems(newUseTaxItems); 1261 } 1262 } 1263 1264 public KualiDecimal getFullDiscountTaxablePrice(KualiDecimal extendedPrice, PurchasingAccountsPayableDocument purapDocument) { 1265 KualiDecimal taxablePrice = KualiDecimal.ZERO; 1266 KualiDecimal taxableLineItemPrice = KualiDecimal.ZERO; 1267 KualiDecimal totalLineItemPrice = KualiDecimal.ZERO; 1268 boolean useTaxIndicator = purapDocument.isUseTaxIndicator(); 1269 String deliveryState = getDeliveryState(purapDocument); 1270 1271 // iterate over items and calculate tax if taxable 1272 for (PurApItem item : purapDocument.getItems()) { 1273 if (item.getItemType().isLineItemIndicator()) { 1274 //only when extended price exists 1275 if (ObjectUtils.isNotNull(item.getExtendedPrice())) { 1276 if (isTaxable(useTaxIndicator, deliveryState, item)) { 1277 taxableLineItemPrice = taxableLineItemPrice.add(item.getExtendedPrice()); 1278 totalLineItemPrice = totalLineItemPrice.add(item.getExtendedPrice()); 1279 } else { 1280 totalLineItemPrice = totalLineItemPrice.add(item.getExtendedPrice()); 1281 } 1282 } 1283 } 1284 } 1285 1286 //check nonzero so no divide by zero errors, and make sure extended price is not null 1287 if (totalLineItemPrice.isNonZero() && ObjectUtils.isNotNull(extendedPrice)) { 1288 taxablePrice = taxableLineItemPrice.divide(totalLineItemPrice).multiply(extendedPrice); 1289 } 1290 1291 return taxablePrice; 1292 } 1293 1294 @Override 1295 public void prorateForTradeInAndFullOrderDiscount(PurchasingAccountsPayableDocument purDoc) { 1296 1297 if (purDoc instanceof VendorCreditMemoDocument) { 1298 throw new RuntimeException("This method not applicable for VCM documents"); 1299 } 1300 1301 //TODO: are we throwing sufficient errors in this method? 1302 PurApItem fullOrderDiscount = null; 1303 PurApItem tradeIn = null; 1304 KualiDecimal totalAmount = KualiDecimal.ZERO; 1305 KualiDecimal totalTaxAmount = KualiDecimal.ZERO; 1306 1307 List<PurApAccountingLine> distributedAccounts = null; 1308 List<SourceAccountingLine> summaryAccounts = null; 1309 1310 // iterate through below the line and grab FoD and TrdIn. 1311 for (PurApItem item : purDoc.getItems()) { 1312 if (item.getItemTypeCode().equals(PurapConstants.ItemTypeCodes.ITEM_TYPE_ORDER_DISCOUNT_CODE)) { 1313 fullOrderDiscount = item; 1314 } else if (item.getItemTypeCode().equals(PurapConstants.ItemTypeCodes.ITEM_TYPE_TRADE_IN_CODE)) { 1315 tradeIn = item; 1316 } 1317 } 1318 // If Discount is not null or zero get proration list for all non misc items and set (if not empty?) 1319 if (fullOrderDiscount != null && 1320 fullOrderDiscount.getExtendedPrice() != null && 1321 fullOrderDiscount.getExtendedPrice().isNonZero()) { 1322 1323 // empty 1324 KNSGlobalVariables.getMessageList().add("Full order discount accounts cleared and regenerated"); 1325 fullOrderDiscount.getSourceAccountingLines().clear(); 1326 //total amount is pretax dollars 1327 totalAmount = purDoc.getTotalDollarAmountAboveLineItems().subtract(purDoc.getTotalTaxAmountAboveLineItems()); 1328 totalTaxAmount = purDoc.getTotalTaxAmountAboveLineItems(); 1329 1330 //Before we generate account summary, we should update the account amounts first. 1331 purapAccountingService.updateAccountAmounts(purDoc); 1332 1333 //calculate tax 1334 boolean salesTaxInd = SpringContext.getBean(ParameterService.class).getParameterValueAsBoolean(OleParameterConstants.PURCHASING_DOCUMENT.class, PurapParameterConstants.ENABLE_SALES_TAX_IND); 1335 boolean useTaxIndicator = purDoc.isUseTaxIndicator(); 1336 1337 if (salesTaxInd == true && (ObjectUtils.isNull(fullOrderDiscount.getItemTaxAmount()) && useTaxIndicator == false)) { 1338 KualiDecimal discountAmount = fullOrderDiscount.getExtendedPrice(); 1339 KualiDecimal discountTaxAmount = discountAmount.divide(totalAmount).multiply(totalTaxAmount); 1340 1341 fullOrderDiscount.setItemTaxAmount(discountTaxAmount); 1342 } 1343 1344 //generate summary 1345 summaryAccounts = purapAccountingService.generateSummary(PurApItemUtils.getAboveTheLineOnly(purDoc.getItems())); 1346 1347 if (summaryAccounts.size() == 0) { 1348 if (purDoc.shouldGiveErrorForEmptyAccountsProration()) { 1349 GlobalVariables.getMessageMap().putError(PurapConstants.ITEM_TAB_ERROR_PROPERTY, PurapKeyConstants.ERROR_SUMMARY_ACCOUNTS_LIST_EMPTY, "full order discount"); 1350 } 1351 } else { 1352 //prorate accounts 1353 distributedAccounts = purapAccountingService.generateAccountDistributionForProration(summaryAccounts, totalAmount.add(totalTaxAmount), 2, fullOrderDiscount.getAccountingLineClass()); 1354 1355 for (PurApAccountingLine distributedAccount : distributedAccounts) { 1356 BigDecimal percent = distributedAccount.getAccountLinePercent(); 1357 BigDecimal roundedPercent = new BigDecimal(Math.round(percent.doubleValue())); 1358 distributedAccount.setAccountLinePercent(roundedPercent); 1359 } 1360 1361 //update amounts on distributed accounts 1362 purapAccountingService.updateAccountAmountsWithTotal(distributedAccounts, totalAmount, 1363 fullOrderDiscount.getTotalAmount()); 1364 1365 fullOrderDiscount.setSourceAccountingLines(distributedAccounts); 1366 } 1367 } else if (fullOrderDiscount != null && 1368 (fullOrderDiscount.getExtendedPrice() == null || fullOrderDiscount.getExtendedPrice().isZero())) { 1369 fullOrderDiscount.getSourceAccountingLines().clear(); 1370 } 1371 1372 // If tradeIn is not null or zero get proration list for all non misc items and set (if not empty?) 1373 if (tradeIn != null && tradeIn.getExtendedPrice() != null && tradeIn.getExtendedPrice().isNonZero()) { 1374 1375 tradeIn.getSourceAccountingLines().clear(); 1376 1377 totalAmount = purDoc.getTotalDollarAmountForTradeIn(); 1378 KualiDecimal tradeInTotalAmount = tradeIn.getTotalAmount(); 1379 //Before we generate account summary, we should update the account amounts first. 1380 purapAccountingService.updateAccountAmounts(purDoc); 1381 1382 //Before generating the summary, lets replace the object code in a cloned accounts collection sothat we can 1383 //consolidate all the modified object codes during summary generation. 1384 List<PurApItem> clonedTradeInItems = new ArrayList(); 1385 Collection<String> objectSubTypesRequiringQty = SpringContext.getBean(ParameterService.class).getParameterValuesAsString(OleParameterConstants.PURCHASING_DOCUMENT.class, PurapParameterConstants.OBJECT_SUB_TYPES_REQUIRING_QUANTITY); 1386 Collection<String> purchasingObjectSubTypes = SpringContext.getBean(ParameterService.class).getParameterValuesAsString(OleParameterConstants.CAPITAL_ASSET_BUILDER_DOCUMENT.class, PurapParameterConstants.PURCHASING_OBJECT_SUB_TYPES); 1387 1388 String tradeInCapitalObjectCode = SpringContext.getBean(ParameterService.class).getParameterValueAsString(PurapConstants.PURAP_NAMESPACE, "Document", "TRADE_IN_OBJECT_CODE_FOR_CAPITAL_ASSET"); 1389 String tradeInCapitalLeaseObjCd = SpringContext.getBean(ParameterService.class).getParameterValueAsString(PurapConstants.PURAP_NAMESPACE, "Document", "TRADE_IN_OBJECT_CODE_FOR_CAPITAL_LEASE"); 1390 1391 for (PurApItem item : purDoc.getTradeInItems()) { 1392 PurApItem cloneItem = (PurApItem) ObjectUtils.deepCopy(item); 1393 List<PurApAccountingLine> sourceAccountingLines = cloneItem.getSourceAccountingLines(); 1394 for (PurApAccountingLine accountingLine : sourceAccountingLines) { 1395 if (objectSubTypesRequiringQty.contains(accountingLine.getObjectCode().getFinancialObjectSubTypeCode())) { 1396 accountingLine.setFinancialObjectCode(tradeInCapitalObjectCode); 1397 } else if (purchasingObjectSubTypes.contains(accountingLine.getObjectCode().getFinancialObjectSubTypeCode())) { 1398 accountingLine.setFinancialObjectCode(tradeInCapitalLeaseObjCd); 1399 } 1400 } 1401 clonedTradeInItems.add(cloneItem); 1402 } 1403 1404 1405 summaryAccounts = purapAccountingService.generateSummary(clonedTradeInItems); 1406 if (summaryAccounts.size() == 0) { 1407 if (purDoc.shouldGiveErrorForEmptyAccountsProration()) { 1408 GlobalVariables.getMessageMap().putError(PurapConstants.ITEM_TAB_ERROR_PROPERTY, PurapKeyConstants.ERROR_SUMMARY_ACCOUNTS_LIST_EMPTY, "trade in"); 1409 } 1410 } else { 1411 distributedAccounts = purapAccountingService.generateAccountDistributionForProration(summaryAccounts, totalAmount, 2, tradeIn.getAccountingLineClass()); 1412 for (PurApAccountingLine distributedAccount : distributedAccounts) { 1413 BigDecimal percent = distributedAccount.getAccountLinePercent(); 1414 BigDecimal roundedPercent = new BigDecimal(Math.round(percent.doubleValue())); 1415 distributedAccount.setAccountLinePercent(roundedPercent); 1416 // set the accountAmount same as tradeIn amount not line item's amount 1417 resetAccountAmount(distributedAccount, tradeInTotalAmount); 1418 } 1419 tradeIn.setSourceAccountingLines(distributedAccounts); 1420 } 1421 } 1422 } 1423 1424 private void resetAccountAmount(PurApAccountingLine distributedAccount, KualiDecimal tradeInTotalAmount) { 1425 BigDecimal pct = distributedAccount.getAccountLinePercent(); 1426 BigDecimal amount = tradeInTotalAmount.bigDecimalValue().multiply(pct).divide(new BigDecimal(100)); 1427 distributedAccount.setAmount(new KualiDecimal(amount)); 1428 } 1429 1430 @Override 1431 public void clearAllTaxes(PurchasingAccountsPayableDocument purapDoc) { 1432 if (purapDoc.getItems() != null) { 1433 for (int i = 0; i < purapDoc.getItems().size(); i++) { 1434 PurApItem item = purapDoc.getItems().get(i); 1435 if (purapDoc.isUseTaxIndicator()) { 1436 item.setUseTaxItems(new ArrayList<PurApItemUseTax>()); 1437 } else { 1438 item.setItemTaxAmount(null); 1439 } 1440 } 1441 } 1442 } 1443 1444 /** 1445 * Determines if the item type specified conflict with the Account tax policy. 1446 * 1447 * @param purchasingDocument purchasing document to check 1448 * @param item item to check if in conflict with tax policy 1449 * @return true if item is in conflict, false otherwise 1450 */ 1451 @Override 1452 public boolean isItemTypeConflictWithTaxPolicy(PurchasingDocument purchasingDocument, PurApItem item) { 1453 boolean conflict = false; 1454 1455 String deliveryState = getDeliveryState(purchasingDocument); 1456 if (item.getItemType().isLineItemIndicator()) { 1457 if (item.getItemType().isTaxableIndicator()) { 1458 if (isTaxDisabledForVendor(purchasingDocument)) { 1459 conflict = true; 1460 } 1461 } 1462 // only check account tax policy if accounting line exists 1463 if (!item.getSourceAccountingLines().isEmpty()) { 1464 if (!doesAccountAllowCallToTaxService(deliveryState, item)) { 1465 conflict = true; 1466 } 1467 } 1468 } 1469 return conflict; 1470 } 1471 1472 /** 1473 * Determines if tax is disabled for vendor, in default always returns false 1474 * 1475 * @param purapDocument the PurchasingDocument with a vendor to check 1476 * @return true if tax is disabled, false if it is not - in foundation OLE, tax is never disabled 1477 */ 1478 protected boolean isTaxDisabledForVendor(PurchasingDocument purapDocument) { 1479 return false; 1480 } 1481 1482 public PurapAccountingService getPurapAccountingService() { 1483 return purapAccountingService; 1484 } 1485 1486 public void setPurapAccountingService(PurapAccountingService purapAccountingService) { 1487 this.purapAccountingService = purapAccountingService; 1488 } 1489} 1490