001 /* 002 * Copyright 2011 The Kuali Foundation. 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 package org.kuali.kfs.module.purap.document.service.impl; 017 018 import java.math.BigDecimal; 019 import java.util.ArrayList; 020 import java.util.HashMap; 021 import java.util.Iterator; 022 import java.util.List; 023 import java.util.Map; 024 025 import org.apache.commons.lang.StringUtils; 026 import org.kuali.kfs.integration.cab.CapitalAssetBuilderModuleService; 027 import org.kuali.kfs.integration.purap.CapitalAssetSystem; 028 import org.kuali.kfs.module.purap.PurapConstants; 029 import org.kuali.kfs.module.purap.PurapPropertyConstants; 030 import org.kuali.kfs.module.purap.PurapRuleConstants; 031 import org.kuali.kfs.module.purap.businessobject.PurApAccountingLine; 032 import org.kuali.kfs.module.purap.businessobject.PurApItem; 033 import org.kuali.kfs.module.purap.businessobject.PurchasingCapitalAssetItem; 034 import org.kuali.kfs.module.purap.businessobject.RequisitionCapitalAssetItem; 035 import org.kuali.kfs.module.purap.businessobject.RequisitionCapitalAssetSystem; 036 import org.kuali.kfs.module.purap.businessobject.RequisitionItem; 037 import org.kuali.kfs.module.purap.document.PurchaseOrderDocument; 038 import org.kuali.kfs.module.purap.document.PurchasingDocument; 039 import org.kuali.kfs.module.purap.document.RequisitionDocument; 040 import org.kuali.kfs.module.purap.document.dataaccess.RequisitionDao; 041 import org.kuali.kfs.module.purap.document.service.PurapService; 042 import org.kuali.kfs.module.purap.document.service.RequisitionService; 043 import org.kuali.kfs.sys.context.SpringContext; 044 import org.kuali.kfs.sys.service.PostalCodeValidationService; 045 import org.kuali.kfs.sys.service.UniversityDateService; 046 import org.kuali.kfs.vnd.businessobject.VendorCommodityCode; 047 import org.kuali.kfs.vnd.businessobject.VendorContract; 048 import org.kuali.kfs.vnd.businessobject.VendorDetail; 049 import org.kuali.kfs.vnd.document.service.VendorService; 050 import org.kuali.rice.kew.exception.WorkflowException; 051 import org.kuali.rice.kim.bo.Person; 052 import org.kuali.rice.kim.service.PersonService; 053 import org.kuali.rice.kns.bo.Note; 054 import org.kuali.rice.kns.service.BusinessObjectService; 055 import org.kuali.rice.kns.service.DateTimeService; 056 import org.kuali.rice.kns.service.DocumentService; 057 import org.kuali.rice.kns.service.KualiConfigurationService; 058 import org.kuali.rice.kns.service.KualiRuleService; 059 import org.kuali.rice.kns.service.ParameterService; 060 import org.kuali.rice.kns.util.KualiDecimal; 061 import org.kuali.rice.kns.util.ObjectUtils; 062 import org.springframework.transaction.annotation.Transactional; 063 064 065 066 /** 067 * Implementation of RequisitionService 068 */ 069 @Transactional 070 public class RequisitionServiceImpl implements RequisitionService { 071 private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(RequisitionServiceImpl.class); 072 073 private BusinessObjectService businessObjectService; 074 private CapitalAssetBuilderModuleService capitalAssetBuilderModuleService; 075 private DateTimeService dateTimeService; 076 private DocumentService documentService; 077 private KualiRuleService ruleService; 078 private KualiConfigurationService kualiConfigurationService; 079 private ParameterService parameterService; 080 private PersonService<Person> personService; 081 private PostalCodeValidationService postalCodeValidationService; 082 private PurapService purapService; 083 private RequisitionDao requisitionDao; 084 private UniversityDateService universityDateService; 085 private VendorService vendorService; 086 087 public PurchasingCapitalAssetItem createCamsItem(PurchasingDocument purDoc, PurApItem purapItem) { 088 PurchasingCapitalAssetItem camsItem = new RequisitionCapitalAssetItem(); 089 camsItem.setItemIdentifier(purapItem.getItemIdentifier()); 090 // If the system type is INDIVIDUAL then for each of the capital asset items, we need a system attached to it. 091 if (purDoc.getCapitalAssetSystemTypeCode().equals(PurapConstants.CapitalAssetTabStrings.INDIVIDUAL_ASSETS)) { 092 CapitalAssetSystem resultSystem = new RequisitionCapitalAssetSystem(); 093 camsItem.setPurchasingCapitalAssetSystem(resultSystem); 094 } 095 camsItem.setPurchasingDocument(purDoc); 096 097 return camsItem; 098 } 099 100 public CapitalAssetSystem createCapitalAssetSystem() { 101 CapitalAssetSystem resultSystem = new RequisitionCapitalAssetSystem(); 102 return resultSystem; 103 } 104 105 /** 106 * @see org.kuali.kfs.module.purap.document.service.RequisitionService#getRequisitionById(java.lang.Integer) 107 */ 108 public RequisitionDocument getRequisitionById(Integer id) { 109 String documentNumber = requisitionDao.getDocumentNumberForRequisitionId(id); 110 if (ObjectUtils.isNotNull(documentNumber)) { 111 try { 112 RequisitionDocument doc = (RequisitionDocument) documentService.getByDocumentHeaderId(documentNumber); 113 114 return doc; 115 } 116 catch (WorkflowException e) { 117 String errorMessage = "Error getting requisition document from document service"; 118 LOG.error("getRequisitionById() " + errorMessage, e); 119 throw new RuntimeException(errorMessage, e); 120 } 121 } 122 123 return null; 124 } 125 126 /** 127 * @see org.kuali.kfs.module.purap.document.service.RequisitionService#isAutomaticPurchaseOrderAllowed(org.kuali.kfs.module.purap.document.RequisitionDocument) 128 */ 129 public boolean isAutomaticPurchaseOrderAllowed(RequisitionDocument requisition) { 130 LOG.debug("isAutomaticPurchaseOrderAllowed() started"); 131 132 /* 133 * The private checkAutomaticPurchaseOrderRules method contains rules to check if a requisition can become an APO (Automatic 134 * Purchase Order). The method returns a string containing the reason why this method should return false. Save the reason 135 * as a note on the requisition. 136 */ 137 String note = checkAutomaticPurchaseOrderRules(requisition); 138 if (StringUtils.isNotEmpty(note)) { 139 note = PurapConstants.REQ_REASON_NOT_APO + note; 140 try { 141 Note apoNote = documentService.createNoteFromDocument(requisition, note); 142 documentService.addNoteToDocument(requisition, apoNote); 143 } 144 catch (Exception e) { 145 throw new RuntimeException(PurapConstants.REQ_UNABLE_TO_CREATE_NOTE + " " + e); 146 } 147 148 LOG.debug("isAutomaticPurchaseOrderAllowed() return false; " + note); 149 return false; 150 } 151 152 LOG.debug("isAutomaticPurchaseOrderAllowed() You made it! Your REQ can become an APO; return true."); 153 return true; 154 } 155 156 /** 157 * Checks the rule for Automatic Purchase Order eligibility of the requisition and return a String containing the reason why the 158 * requisition was not eligible to become an APO if it was not eligible, or return an empty String if the requisition is 159 * eligible to become an APO 160 * 161 * @param requisition the requisition document to be checked for APO eligibility. 162 * @return String containing the reason why the requisition was not eligible to become an APO if it was not eligible, or an 163 * empty String if the requisition is eligible to become an APO. 164 */ 165 protected String checkAutomaticPurchaseOrderRules(RequisitionDocument requisition) { 166 String requisitionSource = requisition.getRequisitionSourceCode(); 167 KualiDecimal reqTotal = requisition.getTotalDollarAmount(); 168 KualiDecimal apoLimit = purapService.getApoLimit(requisition.getVendorContractGeneratedIdentifier(), requisition.getChartOfAccountsCode(), requisition.getOrganizationCode()); 169 requisition.setOrganizationAutomaticPurchaseOrderLimit(apoLimit); 170 171 LOG.debug("isAPO() reqId = " + requisition.getPurapDocumentIdentifier() + "; apoLimit = " + apoLimit + "; reqTotal = " + reqTotal); 172 if (apoLimit == null) { 173 return "APO limit is empty."; 174 } 175 else { 176 if (reqTotal.compareTo(apoLimit) == 1) { 177 return "Requisition total is greater than the APO limit."; 178 } 179 } 180 181 if (reqTotal.compareTo(KualiDecimal.ZERO) <= 0) { 182 return "Requisition total is not greater than zero."; 183 } 184 185 LOG.debug("isAPO() vendor #" + requisition.getVendorHeaderGeneratedIdentifier() + "-" + requisition.getVendorDetailAssignedIdentifier()); 186 if (requisition.getVendorHeaderGeneratedIdentifier() == null || requisition.getVendorDetailAssignedIdentifier() == null) { 187 return "Vendor was not selected from the vendor database."; 188 } 189 else { 190 VendorDetail vendorDetail = vendorService.getVendorDetail(requisition.getVendorHeaderGeneratedIdentifier(), requisition.getVendorDetailAssignedIdentifier()); 191 if (vendorDetail == null) { 192 return "Error retrieving vendor from the database."; 193 } 194 if ( StringUtils.isBlank(requisition.getVendorLine1Address()) || 195 StringUtils.isBlank(requisition.getVendorCityName()) || 196 StringUtils.isBlank(requisition.getVendorCountryCode())) { 197 return "Requisition does not have all of the vendor address fields that are required for Purchase Order."; 198 } 199 requisition.setVendorRestrictedIndicator(vendorDetail.getVendorRestrictedIndicator()); 200 if (requisition.getVendorRestrictedIndicator() != null && requisition.getVendorRestrictedIndicator()) { 201 return "Selected vendor is marked as restricted."; 202 } 203 requisition.setVendorDetail(vendorDetail); 204 205 if ((!PurapConstants.RequisitionSources.B2B.equals(requisitionSource)) && ObjectUtils.isNull(requisition.getVendorContractGeneratedIdentifier())) { 206 Person initiator = getPersonService().getPerson(requisition.getDocumentHeader().getWorkflowDocument().getInitiatorPrincipalId()); 207 VendorContract b2bContract = vendorService.getVendorB2BContract(vendorDetail, initiator.getCampusCode()); 208 if (b2bContract != null) { 209 return "Standard requisition with no contract selected but a B2B contract exists for the selected vendor."; 210 } 211 } 212 } 213 214 //if vendor address isn't complete, no APO 215 if (StringUtils.isBlank(requisition.getVendorLine1Address()) || 216 StringUtils.isBlank(requisition.getVendorCityName()) || 217 StringUtils.isBlank(requisition.getVendorCountryCode()) || 218 !postalCodeValidationService.validateAddress(requisition.getVendorCountryCode(), requisition.getVendorStateCode(), requisition.getVendorPostalCode(), "", "")) { 219 return "Requistion does not contain a complete vendor address"; 220 } 221 222 // These are needed for commodity codes. They are put in here so that 223 // we don't have to loop through items too many times. 224 String purchaseOrderRequiresCommodityCode = parameterService.getParameterValue(PurchaseOrderDocument.class, PurapRuleConstants.ITEMS_REQUIRE_COMMODITY_CODE_IND); 225 boolean commodityCodeRequired = purchaseOrderRequiresCommodityCode.equals("Y"); 226 227 for (Iterator iter = requisition.getItems().iterator(); iter.hasNext();) { 228 RequisitionItem item = (RequisitionItem) iter.next(); 229 if (item.isItemRestrictedIndicator()) { 230 return "Requisition contains an item that is marked as restricted."; 231 } 232 233 //We only need to check the commodity codes if this is an above the line item. 234 if (item.getItemType().isLineItemIndicator()) { 235 String commodityCodesReason = ""; 236 List<VendorCommodityCode> vendorCommodityCodes = commodityCodeRequired ? requisition.getVendorDetail().getVendorCommodities() : null; 237 commodityCodesReason = checkAPORulesPerItemForCommodityCodes(item, vendorCommodityCodes, commodityCodeRequired); 238 if (StringUtils.isNotBlank(commodityCodesReason)) { 239 return commodityCodesReason; 240 } 241 } 242 243 if (PurapConstants.ItemTypeCodes.ITEM_TYPE_ORDER_DISCOUNT_CODE.equals(item.getItemType().getItemTypeCode()) || PurapConstants.ItemTypeCodes.ITEM_TYPE_TRADE_IN_CODE.equals(item.getItemType().getItemTypeCode())) { 244 if ((item.getItemUnitPrice() != null) && ((BigDecimal.ZERO.compareTo(item.getItemUnitPrice())) != 0)) { 245 // discount or trade-in item has unit price that is not empty or zero 246 return "Requisition contains a " + item.getItemType().getItemTypeDescription() + " item, so it does not qualify as an APO."; 247 } 248 } 249 250 if (!PurapConstants.RequisitionSources.B2B.equals(requisitionSource)) { 251 for (PurApAccountingLine accountingLine : item.getSourceAccountingLines()) { 252 if (capitalAssetBuilderModuleService.doesAccountingLineFailAutomaticPurchaseOrderRules(accountingLine)) { 253 return "Requisition contains accounting line with capital object level"; 254 } 255 } 256 } 257 258 }// endfor items 259 260 if (capitalAssetBuilderModuleService.doesDocumentFailAutomaticPurchaseOrderRules(requisition)) { 261 return "Requisition contains capital asset items."; 262 } 263 264 if (StringUtils.isNotEmpty(requisition.getRecurringPaymentTypeCode())) { 265 return "Payment type is marked as recurring."; 266 } 267 268 if ((requisition.getPurchaseOrderTotalLimit() != null) && (KualiDecimal.ZERO.compareTo(requisition.getPurchaseOrderTotalLimit()) != 0)) { 269 LOG.debug("isAPO() po total limit is not null and not equal to zero; return false."); 270 return "The 'PO not to exceed' amount has been entered."; 271 } 272 273 if (StringUtils.isNotEmpty(requisition.getAlternate1VendorName()) || StringUtils.isNotEmpty(requisition.getAlternate2VendorName()) || StringUtils.isNotEmpty(requisition.getAlternate3VendorName()) || StringUtils.isNotEmpty(requisition.getAlternate4VendorName()) || StringUtils.isNotEmpty(requisition.getAlternate5VendorName())) { 274 LOG.debug("isAPO() alternate vendor name exists; return false."); 275 return "Requisition contains additional suggested vendor names."; 276 } 277 278 if (requisition.isPostingYearNext() && !purapService.isTodayWithinApoAllowedRange()) { 279 return "Requisition is set to encumber next fiscal year and approval is not within APO allowed date range."; 280 } 281 282 return ""; 283 } 284 285 /** 286 * Checks the APO rules for Commodity Codes. 287 * The rules are as follow: 288 * 1. If an institution does not require a commodity code on a requisition but 289 * does require a commodity code on a purchase order: 290 * a. If the requisition qualifies for an APO and the commodity code is blank 291 * on any line item then the system should use the default commodity code 292 * for the vendor. 293 * b. If there is not a default commodity code for the vendor then the 294 * requisition is not eligible to become an APO. 295 * 2. The commodity codes where the restricted indicator is Y should disallow 296 * the requisition from becoming an APO. 297 * 3. If the commodity code is Inactive when the requisition is finally approved 298 * do not allow the requisition to become an APO. 299 * 300 * @param purItem 301 * @param vendorCommodityCodes 302 * @param commodityCodeRequired 303 * @return 304 */ 305 protected String checkAPORulesPerItemForCommodityCodes(RequisitionItem purItem, List<VendorCommodityCode>vendorCommodityCodes, boolean commodityCodeRequired) { 306 // If the commodity code is blank on any line item and a commodity code is required, 307 // then the system should use the default commodity code for the vendor 308 if (purItem.getCommodityCode() == null && commodityCodeRequired) { 309 for (VendorCommodityCode vcc : vendorCommodityCodes) { 310 if (vcc.isCommodityDefaultIndicator()) { 311 purItem.setCommodityCode(vcc.getCommodityCode()); 312 purItem.setPurchasingCommodityCode(vcc.getPurchasingCommodityCode()); 313 } 314 } 315 } 316 if (purItem.getCommodityCode() == null) { 317 // If there is not a default commodity code for the vendor then the requisition is not eligible to become an APO. 318 if (commodityCodeRequired) 319 return "There are missing commodity code(s)."; 320 } 321 else if (!purItem.getCommodityCode().isActive()) { 322 return "Requisition contains inactive commodity codes."; 323 } 324 else if (purItem.getCommodityCode().isRestrictedItemsIndicator()) { 325 return "Requisition contains an item with a restricted commodity code."; 326 } 327 return ""; 328 } 329 330 /** 331 * @see org.kuali.kfs.module.purap.document.service.RequisitionService#getRequisitionsAwaitingContractManagerAssignment() 332 */ 333 public List<RequisitionDocument> getRequisitionsAwaitingContractManagerAssignment() { 334 Map fieldValues = new HashMap(); 335 fieldValues.put(PurapPropertyConstants.STATUS_CODE, PurapConstants.RequisitionStatuses.AWAIT_CONTRACT_MANAGER_ASSGN); 336 List<RequisitionDocument> unassignedRequisitions = new ArrayList(SpringContext.getBean(BusinessObjectService.class).findMatchingOrderBy(RequisitionDocument.class, fieldValues, PurapPropertyConstants.PURAP_DOC_ID, true)); 337 return unassignedRequisitions; 338 } 339 340 /** 341 * @see org.kuali.kfs.module.purap.document.service.RequisitionService#getCountOfRequisitionsAwaitingContractManagerAssignment() 342 */ 343 public int getCountOfRequisitionsAwaitingContractManagerAssignment() { 344 List<RequisitionDocument> unassignedRequisitions = getRequisitionsAwaitingContractManagerAssignment(); 345 if (ObjectUtils.isNotNull(unassignedRequisitions)) { 346 return unassignedRequisitions.size(); 347 } 348 else { 349 return 0; 350 } 351 } 352 353 public void setBusinessObjectService(BusinessObjectService boService) { 354 this.businessObjectService = boService; 355 } 356 357 public void setDocumentService(DocumentService documentService) { 358 this.documentService = documentService; 359 } 360 361 public void setRequisitionDao(RequisitionDao requisitionDao) { 362 this.requisitionDao = requisitionDao; 363 } 364 365 public void setPurapService(PurapService purapService) { 366 this.purapService = purapService; 367 } 368 369 public KualiRuleService getRuleService() { 370 return ruleService; 371 } 372 373 public void setRuleService(KualiRuleService ruleService) { 374 this.ruleService = ruleService; 375 } 376 377 public void setParameterService(ParameterService parameterService) { 378 this.parameterService = parameterService; 379 } 380 381 public void setDateTimeService(DateTimeService dateTimeService) { 382 this.dateTimeService = dateTimeService; 383 } 384 385 public void setUniversityDateService(UniversityDateService universityDateService) { 386 this.universityDateService = universityDateService; 387 } 388 389 public void setVendorService(VendorService vendorService) { 390 this.vendorService = vendorService; 391 } 392 393 public void setKualiConfigurationService(KualiConfigurationService kualiConfigurationService) { 394 this.kualiConfigurationService = kualiConfigurationService; 395 } 396 397 public void setCapitalAssetBuilderModuleService(CapitalAssetBuilderModuleService capitalAssetBuilderModuleService) { 398 this.capitalAssetBuilderModuleService = capitalAssetBuilderModuleService; 399 } 400 401 public void setPostalCodeValidationService(PostalCodeValidationService postalCodeValidationService) { 402 this.postalCodeValidationService = postalCodeValidationService; 403 } 404 405 /** 406 * @return Returns the personService. 407 */ 408 protected PersonService<Person> getPersonService() { 409 if(personService==null) 410 personService = SpringContext.getBean(PersonService.class); 411 return personService; 412 } 413 414 }