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.vnd.document; 017 018 import java.util.ArrayList; 019 import java.util.HashMap; 020 import java.util.List; 021 import java.util.Map; 022 023 import org.apache.commons.lang.StringUtils; 024 import org.kuali.kfs.sys.context.SpringContext; 025 import org.kuali.kfs.sys.document.FinancialSystemMaintainable; 026 import org.kuali.kfs.vnd.VendorConstants; 027 import org.kuali.kfs.vnd.VendorKeyConstants; 028 import org.kuali.kfs.vnd.VendorParameterConstants; 029 import org.kuali.kfs.vnd.VendorPropertyConstants; 030 import org.kuali.kfs.vnd.VendorUtils; 031 import org.kuali.kfs.vnd.businessobject.VendorDetail; 032 import org.kuali.kfs.vnd.businessobject.VendorHeader; 033 import org.kuali.kfs.vnd.businessobject.VendorTaxChange; 034 import org.kuali.kfs.vnd.document.service.VendorService; 035 import org.kuali.rice.kim.bo.Person; 036 import org.kuali.rice.kns.bo.DocumentHeader; 037 import org.kuali.rice.kns.bo.Note; 038 import org.kuali.rice.kns.bo.PersistableBusinessObject; 039 import org.kuali.rice.kns.document.MaintenanceDocument; 040 import org.kuali.rice.kns.document.MaintenanceLock; 041 import org.kuali.rice.kns.service.BusinessObjectService; 042 import org.kuali.rice.kns.service.DateTimeService; 043 import org.kuali.rice.kns.service.NoteService; 044 import org.kuali.rice.kns.service.ParameterService; 045 import org.kuali.rice.kns.util.GlobalVariables; 046 import org.kuali.rice.kns.util.ObjectUtils; 047 import org.kuali.rice.kns.workflow.service.KualiWorkflowDocument; 048 049 public class VendorMaintainableImpl extends FinancialSystemMaintainable { 050 private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(VendorMaintainableImpl.class); 051 052 /** 053 * @see org.kuali.rice.kns.maintenance.KualiMaintainableImpl#setGenerateDefaultValues(boolean) 054 */ 055 @Override 056 public void setGenerateDefaultValues(String docTypeName) { 057 super.setGenerateDefaultValues(docTypeName); 058 if (this.getBusinessObject().getBoNotes().isEmpty()) { 059 setVendorCreateAndUpdateNote(VendorConstants.VendorCreateAndUpdateNotePrefixes.ADD); 060 } 061 } 062 063 /** 064 * Overrides the kuali default documents title with a Vendor-specific document title style 065 * 066 * @see org.kuali.rice.kns.maintenance.KualiMaintainableImpl#getDocumentTitle(org.kuali.rice.kns.document.MaintenanceDocument) 067 */ 068 @Override 069 public String getDocumentTitle(MaintenanceDocument document) { 070 String documentTitle = ""; 071 // Check if we are choosing to override the Kuali default document title. 072 if (SpringContext.getBean(ParameterService.class).getIndicatorParameter(VendorDetail.class, VendorParameterConstants.OVERRIDE_VENDOR_DOC_TITLE)) { 073 // We are overriding the standard with a Vendor-specific document title style. 074 if (document.isOldBusinessObjectInDocument()) { 075 documentTitle = "Edit Vendor - "; 076 } 077 else { 078 documentTitle = "New Vendor - "; 079 } 080 081 try { 082 Person initUser = SpringContext.getBean(org.kuali.rice.kim.service.PersonService.class).getPersonByPrincipalName(document.getDocumentHeader().getWorkflowDocument().getInitiatorNetworkId()); 083 documentTitle += initUser.getCampusCode(); 084 } 085 catch (Exception e) { 086 throw new RuntimeException("Document Initiator not found " + e.getMessage()); 087 } 088 089 VendorDetail newBo = (VendorDetail) document.getNewMaintainableObject().getBusinessObject(); 090 091 if (StringUtils.isNotBlank(newBo.getVendorName())) { 092 documentTitle += " '" + newBo.getVendorName() + "'"; 093 } 094 else { 095 if (StringUtils.isNotBlank(newBo.getVendorFirstName())) { 096 documentTitle += " '" + newBo.getVendorFirstName() + " "; 097 if (StringUtils.isBlank(newBo.getVendorLastName())) { 098 documentTitle += "'"; 099 } 100 } 101 102 if (StringUtils.isNotBlank(newBo.getVendorLastName())) { 103 if (StringUtils.isBlank(newBo.getVendorFirstName())) { 104 documentTitle += " '"; 105 } 106 documentTitle += newBo.getVendorLastName() + "'"; 107 } 108 } 109 110 if (newBo.getVendorHeader().getVendorForeignIndicator()) { 111 documentTitle += " (F)"; 112 } 113 114 if (!newBo.isVendorParentIndicator()) { 115 documentTitle += " (D)"; 116 } 117 } 118 else { // We are using the Kuali default document title. 119 documentTitle = super.getDocumentTitle(document); 120 } 121 return documentTitle; 122 } 123 124 @Override 125 public void doRouteStatusChange(DocumentHeader header) { 126 super.doRouteStatusChange(header); 127 VendorDetail vendorDetail = (VendorDetail) getBusinessObject(); 128 KualiWorkflowDocument workflowDoc = header.getWorkflowDocument(); 129 130 // This code is only executed when the final approval occurs 131 if (workflowDoc.stateIsProcessed()) { 132 // This id and versionNumber null check is needed here since those fields are always null for a fresh maintenance doc. 133 if (vendorDetail.isVendorParentIndicator() && vendorDetail.getVendorHeaderGeneratedIdentifier() != null) { 134 VendorDetail previousParent = SpringContext.getBean(VendorService.class).getParentVendor(vendorDetail.getVendorHeaderGeneratedIdentifier()); 135 // We'll only need to do the following if the previousParent is not the same as the current vendorDetail, because 136 // the following lines are for vendor parent indicator changes. 137 if (vendorDetail.getVendorDetailAssignedIdentifier() == null || 138 previousParent.getVendorHeaderGeneratedIdentifier().intValue() != vendorDetail.getVendorHeaderGeneratedIdentifier().intValue() || 139 previousParent.getVendorDetailAssignedIdentifier().intValue() != vendorDetail.getVendorDetailAssignedIdentifier().intValue()) { 140 previousParent.setVendorParentIndicator(false); 141 addNoteForParentIndicatorChange(vendorDetail, previousParent, header.getDocumentNumber()); 142 SpringContext.getBean(BusinessObjectService.class).save(previousParent); 143 } 144 } 145 146 // If this is a pre-existing parent vendor, and if the Tax Number or the Tax Type Code will change, log the change in the 147 // Tax Change table. 148 if (vendorDetail.isVendorParentIndicator()) { 149 VendorDetail oldVendorDetail = SpringContext.getBean(VendorService.class).getVendorDetail(vendorDetail.getVendorHeaderGeneratedIdentifier(), vendorDetail.getVendorDetailAssignedIdentifier()); 150 if (ObjectUtils.isNotNull(oldVendorDetail)) { 151 VendorHeader oldVendorHeader = oldVendorDetail.getVendorHeader(); 152 VendorHeader newVendorHeader = vendorDetail.getVendorHeader(); 153 154 if (ObjectUtils.isNotNull(oldVendorHeader)) { // Does not apply if this is a new parent vendor. 155 String oldVendorTaxNumber = oldVendorHeader.getVendorTaxNumber(); 156 String oldVendorTaxTypeCode = oldVendorHeader.getVendorTaxTypeCode(); 157 158 String vendorTaxNumber = newVendorHeader.getVendorTaxNumber(); 159 String vendorTaxTypeCode = newVendorHeader.getVendorTaxTypeCode(); 160 161 if ((!StringUtils.equals(vendorTaxNumber, oldVendorTaxNumber)) || (!StringUtils.equals(vendorTaxTypeCode, oldVendorTaxTypeCode))) { 162 VendorTaxChange taxChange = new VendorTaxChange(vendorDetail.getVendorHeaderGeneratedIdentifier(), SpringContext.getBean(DateTimeService.class).getCurrentTimestamp(), oldVendorTaxNumber, oldVendorTaxTypeCode, GlobalVariables.getUserSession().getPerson().getPrincipalId()); 163 SpringContext.getBean(BusinessObjectService.class).save(taxChange); 164 } 165 } 166 } 167 } 168 169 }//endif stateIsProcessed() 170 } 171 172 /** 173 * Add a note to the previous parent vendor to denote that parent vendor indicator change had occurred. 174 * 175 * @param newVendorDetail The current vendor 176 * @param oldVendorDetail The parent vendor of the current vendor prior to this change. 177 * @param documentNumber The document number of the document where we're attempting the parent vendor indicator change. 178 */ 179 private void addNoteForParentIndicatorChange(VendorDetail newVendorDetail, VendorDetail oldVendorDetail, String documentNumber) { 180 String noteText = VendorUtils.buildMessageText(VendorKeyConstants.MESSAGE_VENDOR_PARENT_TO_DIVISION, documentNumber, newVendorDetail.getVendorName() + " (" + newVendorDetail.getVendorNumber() + ")"); 181 Note newBONote = new Note(); 182 newBONote.setNoteText(noteText); 183 try { 184 NoteService noteService = SpringContext.getBean(NoteService.class); 185 newBONote = noteService.createNote(newBONote, oldVendorDetail); 186 noteService.save(newBONote); 187 } 188 catch (Exception e) { 189 throw new RuntimeException("Caught Exception While Trying To Add Note to Vendor", e); 190 } 191 oldVendorDetail.getBoNotes().add(newBONote); 192 193 } 194 195 /** 196 * Refreshes the vendorDetail. Currently we need this mainly for refreshing the soldToVendor object after returning from the 197 * lookup for a sold to vendor. 198 * 199 * @see org.kuali.rice.kns.maintenance.KualiMaintainableImpl#refresh(java.lang.String, java.util.Map, 200 * org.kuali.rice.kns.document.MaintenanceDocument) 201 */ 202 @Override 203 public void refresh(String refreshCaller, Map fieldValues, MaintenanceDocument document) { 204 PersistableBusinessObject oldBo = (PersistableBusinessObject) document.getOldMaintainableObject().getBusinessObject(); 205 if (ObjectUtils.isNotNull(oldBo)) { 206 oldBo.refreshNonUpdateableReferences(); 207 } 208 VendorDetail newBo = (VendorDetail) document.getNewMaintainableObject().getBusinessObject(); 209 // Here we have to temporarily save vendorHeader into a temp object, then put back 210 // the vendorHeader into the newBo after the refresh, so that we don't lose the 211 // values 212 VendorHeader tempHeader = newBo.getVendorHeader(); 213 newBo.refreshNonUpdateableReferences(); 214 newBo.setVendorHeader(tempHeader); 215 super.refresh(refreshCaller, fieldValues, document); 216 } 217 218 /** 219 * Temporarily saves vendorHeader into a temp object, then put back the vendorHeader into the VendorDetail after the refresh, so 220 * that we don't lose the values 221 */ 222 public void refreshBusinessObject() { 223 VendorDetail vd = (VendorDetail) getBusinessObject(); 224 // Here we have to temporarily save vendorHeader into a temp object, then put back 225 // the vendorHeader into the VendorDetail after the refresh, so that we don't lose the 226 // values 227 VendorHeader tempHeader = vd.getVendorHeader(); 228 vd.refreshNonUpdateableReferences(); 229 vd.setVendorHeader(tempHeader); 230 } 231 232 233 /** 234 * Checks whether the vendor has already had a vendor detail assigned id. If not, it will call the private method to set the 235 * detail assigned id. The method will also call the vendorService to determine whether it should save the vendor header (i.e. 236 * if this is a parent) and will save the vendor header accordingly. This is because we are not going to save vendor header 237 * automatically along with the saving of vendor detail, so if the vendor is a parent, we have to save the vendor header 238 * separately. Restriction-related information will be changed based on whether the Vendor Restricted Indicator was changed. If 239 * the Tax Number or Tax Type code have changed, the fact will be recorded with a new record in the Tax Change table. Finally 240 * the method will call the saveBusinessObject( ) of the super class to save the vendor detail. 241 * 242 * @see org.kuali.rice.kns.maintenance.KualiMaintainableImpl#saveBusinessObject() 243 */ 244 @Override 245 public void saveBusinessObject() { 246 VendorDetail vendorDetail = (VendorDetail) super.getBusinessObject(); 247 VendorHeader vendorHeader = vendorDetail.getVendorHeader(); 248 249 // Update miscellaneous information and save the Vendor Header if this is a parent vendor. 250 setVendorName(vendorDetail); 251 vendorHeader.setVendorHeaderGeneratedIdentifier(vendorDetail.getVendorHeaderGeneratedIdentifier()); 252 if (ObjectUtils.isNull(vendorDetail.getVendorDetailAssignedIdentifier())) { 253 setDetailAssignedId(vendorDetail); 254 } 255 if (vendorDetail.isVendorParentIndicator()) { 256 SpringContext.getBean(VendorService.class).saveVendorHeader(vendorDetail); 257 } 258 super.saveBusinessObject(); 259 } 260 261 /** 262 * @see org.kuali.rice.kns.maintenance.KualiMaintainableImpl#processAfterEdit() 263 */ 264 @Override 265 public void processAfterEdit( MaintenanceDocument document, Map<String,String[]> parameters ) { 266 setVendorCreateAndUpdateNote(VendorConstants.VendorCreateAndUpdateNotePrefixes.CHANGE); 267 super.processAfterEdit(document, parameters); 268 } 269 270 /** 271 * Checks whether the previous note was an "Add" with the same document number as this one 272 * 273 * @param prefix String to determine if it is a note "Add" or a note "Change" 274 */ 275 private void setVendorCreateAndUpdateNote(String prefix) { 276 boolean shouldAddNote = true; 277 if (prefix.equals(VendorConstants.VendorCreateAndUpdateNotePrefixes.CHANGE)) { 278 // Check whether the previous note was an "Add" with the same document number as this one 279 if (!this.getBusinessObject().getBoNotes().isEmpty()) { 280 Note previousNote = this.getBusinessObject().getBoNote(this.getBusinessObject().getBoNotes().size() - 1); 281 if (previousNote.getNoteText().contains(this.documentNumber)) { 282 shouldAddNote = false; 283 } 284 } 285 } 286 if (shouldAddNote) { 287 Note newBONote = new Note(); 288 newBONote.setNoteText(prefix + " vendor document ID " + this.documentNumber); 289 try { 290 newBONote = SpringContext.getBean(NoteService.class).createNote(newBONote, this.getBusinessObject()); 291 } 292 catch (Exception e) { 293 throw new RuntimeException("Caught Exception While Trying To Add Note to Vendor", e); 294 } 295 this.getBusinessObject().getBoNotes().add(newBONote); 296 } 297 } 298 299 /** 300 * Concatenates the vendorLastName and a delimiter and the vendorFirstName fields into vendorName field of the vendorDetail 301 * object. 302 * 303 * @param vendorDetail VendorDetail The vendor whose name field we are trying to assign 304 */ 305 private void setVendorName(VendorDetail vendorDetail) { 306 if (vendorDetail.isVendorFirstLastNameIndicator()) { 307 vendorDetail.setVendorName(vendorDetail.getVendorLastName() + VendorConstants.NAME_DELIM + vendorDetail.getVendorFirstName()); 308 } 309 } 310 311 /** 312 * If the vendorFirstLastNameIndicator is true, this method will set the vendor first name and vendor last name fields from the 313 * vendorName field, then set the vendorName field to null. Then it sets the businessObject of this maintainable to the 314 * VendorDetail object that contains our modification to the name fields. 315 * 316 * @see org.kuali.rice.kns.maintenance.Maintainable#saveBusinessObject() 317 */ 318 @Override 319 public void setBusinessObject(PersistableBusinessObject bo) { 320 VendorDetail originalBo = (VendorDetail) bo; 321 String vendorName = originalBo.getVendorName(); 322 if (originalBo.isVendorFirstLastNameIndicator() && ObjectUtils.isNotNull(vendorName)) { 323 int start = vendorName.indexOf(VendorConstants.NAME_DELIM); 324 if (start >= 0) { 325 String lastName = vendorName.substring(0, start); 326 String firstName = new String(); 327 if (start + VendorConstants.NAME_DELIM.length() <= vendorName.length()) { 328 firstName = vendorName.substring(start + VendorConstants.NAME_DELIM.length(), vendorName.length()); 329 } 330 331 originalBo.setVendorFirstName((ObjectUtils.isNotNull(firstName) ? firstName.trim() : firstName)); 332 originalBo.setVendorLastName((ObjectUtils.isNotNull(lastName) ? lastName.trim() : lastName)); 333 originalBo.setVendorName(null); 334 } 335 } 336 this.businessObject = originalBo; 337 } 338 339 /** 340 * Sets a valid detail assigned id to a vendor if the vendor has not had a detail assigned id yet. If this is a new parent whose 341 * header id is also null, this method will assign 0 as the detail assigned id. If this is a new division vendor, it will look 342 * for the count of vendor details in the database whose vendor header id match with the vendor header id of this new division, 343 * then look for the count of vendor details in the database, in a while loop, to find if a vendor detail with the same header 344 * id and detail id as the count has existed. If a vendor with such criteria exists, this method will increment the count 345 * by 1 and look up in the database again. If it does not exist, assign the count as the vendor detail id and change the 346 * boolean flag to stop the loop, because we have already found the valid detail assigned id that we were looking for 347 * 348 * @param vendorDetail VendorDetail The vendor whose detail assigned id we're trying to assign. 349 */ 350 private void setDetailAssignedId(VendorDetail vendorDetail) { 351 // If this is a new parent, let's set the detail id to 0. 352 if (ObjectUtils.isNull(vendorDetail.getVendorHeaderGeneratedIdentifier())) { 353 vendorDetail.setVendorDetailAssignedIdentifier(new Integer(0)); 354 } 355 else { 356 // Try to get the count of all the vendor whose header id is the same as this header id. 357 Map criterias = new HashMap(); 358 criterias.put(VendorPropertyConstants.VENDOR_HEADER_GENERATED_ID, vendorDetail.getVendorHeaderGeneratedIdentifier()); 359 BusinessObjectService boService = SpringContext.getBean(BusinessObjectService.class); 360 int count = boService.countMatching(VendorDetail.class, criterias); 361 boolean validId = false; 362 while (!validId) { 363 criterias.put(VendorPropertyConstants.VENDOR_DETAIL_ASSIGNED_ID, count); 364 int result = boService.countMatching(VendorDetail.class, criterias); 365 if (result > 0) { 366 // increment the detail id by 1 367 count++; 368 } 369 else { 370 // count is a validId, so we'll use count as our vendor detail assigned id 371 validId = true; 372 vendorDetail.setVendorDetailAssignedIdentifier(new Integer(count)); 373 } 374 } 375 } 376 } 377 378 /** 379 * Returns the locking representation of the vendor. If the vendor detail id is not null, call the super class 380 * implementation of generateMaintenanceLocks which will set the locking key to be the header and detail ids. However, if the 381 * detail id is null, that means this is a new vendor (parent or division) and we should ignore locking. 382 * 383 * @see org.kuali.rice.kns.maintenance.Maintainable#generateMaintenanceLocks() 384 */ 385 @Override 386 public List<MaintenanceLock> generateMaintenanceLocks() { 387 if (ObjectUtils.isNotNull(((VendorDetail) getBusinessObject()).getVendorDetailAssignedIdentifier())) { 388 return super.generateMaintenanceLocks(); 389 } 390 else { 391 return new ArrayList(); 392 } 393 } 394 395 /** 396 * Create a new division vendor if the user clicks on the "Create a new division" link. By default, the vendorParentIndicator is 397 * set to true in the constructor of VendorDetail, but if we're creating a new division, it's not a parent, so we need to set 398 * the vendorParentIndicator to false in this case. 399 * 400 * @see org.kuali.rice.kns.maintenance.Maintainable#setupNewFromExisting() 401 */ 402 @Override 403 public void setupNewFromExisting( MaintenanceDocument document, Map<String,String[]> parameters ) { 404 super.setupNewFromExisting(document, parameters); 405 ((VendorDetail) super.getBusinessObject()).setVendorParentIndicator(false); 406 ((VendorDetail) super.getBusinessObject()).setActiveIndicator(true); 407 408 setVendorCreateAndUpdateNote(VendorConstants.VendorCreateAndUpdateNotePrefixes.ADD); 409 } 410 411 /** 412 * @see org.kuali.rice.kns.maintenance.KualiMaintainableImpl#isRelationshipRefreshable(java.lang.Class, java.lang.String) 413 */ 414 @Override 415 protected boolean isRelationshipRefreshable(Class boClass, String relationshipName) { 416 if (VendorDetail.class.isAssignableFrom(boClass) && VendorConstants.VENDOR_HEADER_ATTR.equals(relationshipName)) { 417 return false; 418 } 419 return super.isRelationshipRefreshable(boClass, relationshipName); 420 } 421 422 /** 423 * @see org.kuali.kfs.sys.document.FinancialSystemMaintainable#answerSplitNodeQuestion(java.lang.String) 424 */ 425 @Override 426 protected boolean answerSplitNodeQuestion(String nodeName) throws UnsupportedOperationException { 427 if (nodeName.equals("RequiresApproval")) return SpringContext.getBean(VendorService.class).shouldVendorRouteForApproval(this.documentNumber); 428 return super.answerSplitNodeQuestion(nodeName); 429 } 430 } 431