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.ar.document; 017 018 import java.sql.Date; 019 import java.util.Collection; 020 import java.util.List; 021 import java.util.Map; 022 023 import org.apache.commons.beanutils.PropertyUtils; 024 import org.kuali.kfs.module.ar.ArKeyConstants; 025 import org.kuali.kfs.module.ar.ArPropertyConstants; 026 import org.kuali.kfs.module.ar.businessobject.Customer; 027 import org.kuali.kfs.module.ar.businessobject.CustomerAddress; 028 import org.kuali.kfs.sys.KFSConstants; 029 import org.kuali.kfs.sys.context.SpringContext; 030 import org.kuali.kfs.sys.document.FinancialSystemMaintainable; 031 import org.kuali.kfs.sys.document.FinancialSystemMaintenanceDocument; 032 import org.kuali.rice.kew.exception.WorkflowException; 033 import org.kuali.rice.kim.bo.Person; 034 import org.kuali.rice.kim.service.PersonService; 035 import org.kuali.rice.kns.bo.DocumentHeader; 036 import org.kuali.rice.kns.bo.PersistableBusinessObject; 037 import org.kuali.rice.kns.document.MaintenanceDocument; 038 import org.kuali.rice.kns.maintenance.Maintainable; 039 import org.kuali.rice.kns.service.DateTimeService; 040 import org.kuali.rice.kns.service.DocumentService; 041 import org.kuali.rice.kns.util.KNSConstants; 042 043 public class CustomerMaintenableImpl extends FinancialSystemMaintainable { 044 private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(CustomerMaintenableImpl.class); 045 046 private static final String REQUIRES_APPROVAL_NODE = "RequiresApproval"; 047 private static final String BO_NOTES = "boNotes"; 048 049 private transient DateTimeService dateTimeService; 050 051 @Override 052 @SuppressWarnings("unchecked") 053 public void processAfterPost(MaintenanceDocument document, Map<String, String[]> parameters) { 054 super.processAfterPost(document, parameters); 055 056 // when we create new customer set the customerRecordAddDate to current date 057 if (getMaintenanceAction().equalsIgnoreCase(KNSConstants.MAINTENANCE_NEW_ACTION)) { 058 Customer oldCustomer = (Customer) document.getOldMaintainableObject().getBusinessObject(); 059 Customer newCustomer = (Customer) document.getNewMaintainableObject().getBusinessObject(); 060 Date currentDate = getDateTimeService().getCurrentSqlDate(); 061 newCustomer.setCustomerRecordAddDate(currentDate); 062 newCustomer.setCustomerLastActivityDate(currentDate); 063 } 064 } 065 066 @Override 067 public void doRouteStatusChange(DocumentHeader documentHeader) { 068 super.doRouteStatusChange(documentHeader); 069 070 // if new customer was created => dates have been already updated 071 if (getMaintenanceAction().equalsIgnoreCase(KNSConstants.MAINTENANCE_NEW_ACTION)) 072 return; 073 074 if (documentHeader.getWorkflowDocument().stateIsProcessed()) { 075 DocumentService documentService = SpringContext.getBean(DocumentService.class); 076 try { 077 MaintenanceDocument document = (MaintenanceDocument) documentService.getByDocumentHeaderId(documentHeader.getDocumentNumber()); 078 Customer newCustomer = (Customer) document.getNewMaintainableObject().getBusinessObject(); 079 Customer oldCustomer = (Customer) document.getOldMaintainableObject().getBusinessObject(); 080 updateDates(oldCustomer, newCustomer); 081 } catch (WorkflowException e) { 082 LOG.error("caught exception while handling handleRouteStatusChange -> documentService.getByDocumentHeaderId(" + documentHeader.getDocumentNumber() + "). ", e); 083 084 } 085 } 086 } 087 088 // Update dates (last activity date and address change date) 089 private void updateDates(Customer oldCustomer, Customer newCustomer) { 090 Date currentDate = getDateTimeService().getCurrentSqlDate(); 091 List oldAddresses = oldCustomer.getCustomerAddresses(); 092 List newAddresses = newCustomer.getCustomerAddresses(); 093 boolean addressChangeFlag = false; 094 095 // if new address was added or one of the old addresses was changed/deleted, set customerAddressChangeDate to the current date 096 if (oldAddresses != null && newAddresses != null) { 097 if (oldAddresses.size() != newAddresses.size()) { 098 newCustomer.setCustomerAddressChangeDate(currentDate); 099 newCustomer.setCustomerLastActivityDate(currentDate); 100 addressChangeFlag = true; 101 } 102 else { 103 for (int i = 0; i < oldAddresses.size(); i++) { 104 CustomerAddress oldAddress = (CustomerAddress) oldAddresses.get(i); 105 CustomerAddress newAddress = (CustomerAddress) newAddresses.get(i); 106 if (oldAddress.compareTo(newAddress) != 0) { 107 newCustomer.setCustomerAddressChangeDate(currentDate); 108 newCustomer.setCustomerLastActivityDate(currentDate); 109 addressChangeFlag = true; 110 break; 111 } 112 } 113 } 114 } 115 // if non address related change 116 if (!addressChangeFlag && !oldCustomer.equals(newCustomer)) 117 newCustomer.setCustomerLastActivityDate(currentDate); 118 } 119 120 121 @Override 122 public PersistableBusinessObject initNewCollectionLine(String collectionName) { 123 124 PersistableBusinessObject businessObject = super.initNewCollectionLine(collectionName); 125 Customer customer = (Customer) this.businessObject; 126 127 if (collectionName.equalsIgnoreCase(ArPropertyConstants.CustomerFields.CUSTOMER_TAB_ADDRESSES)) { 128 129 CustomerAddress customerAddress = (CustomerAddress) businessObject; 130 131 // set default address name to customer name 132 customerAddress.setCustomerAddressName(customer.getCustomerName()); 133 134 if (KNSConstants.MAINTENANCE_NEW_ACTION.equalsIgnoreCase(getMaintenanceAction())) { 135 136 boolean hasPrimaryAddress = false; 137 138 for (CustomerAddress tempAddress : customer.getCustomerAddresses()) { 139 if (ArKeyConstants.CustomerConstants.CUSTOMER_ADDRESS_TYPE_CODE_PRIMARY.equalsIgnoreCase(tempAddress.getCustomerAddressTypeCode())) { 140 hasPrimaryAddress = true; 141 break; 142 } 143 } 144 // if maintenance action is NEW and customer already has a primary address set default value for address type code 145 // to "Alternate" 146 if (hasPrimaryAddress) { 147 customerAddress.setCustomerAddressTypeCode(ArKeyConstants.CustomerConstants.CUSTOMER_ADDRESS_TYPE_CODE_ALTERNATE); 148 } 149 // otherwise set default value for address type code to "Primary" 150 else { 151 customerAddress.setCustomerAddressTypeCode(ArKeyConstants.CustomerConstants.CUSTOMER_ADDRESS_TYPE_CODE_PRIMARY); 152 } 153 } 154 155 // if maintenance action is EDIT or COPY set default value for address type code to "Alternate" 156 if (KNSConstants.MAINTENANCE_EDIT_ACTION.equalsIgnoreCase(getMaintenanceAction()) || KNSConstants.MAINTENANCE_COPY_ACTION.equalsIgnoreCase(getMaintenanceAction())) { 157 customerAddress.setCustomerAddressTypeCode(ArKeyConstants.CustomerConstants.CUSTOMER_ADDRESS_TYPE_CODE_ALTERNATE); 158 } 159 160 } 161 162 return businessObject; 163 164 } 165 166 /** 167 * Answers true for 2 conditions... 168 * <li>a)New customer created by non-batch (web) user</li> 169 * <li>b)Any edit to an existing customer record</li> 170 * 171 * @see org.kuali.kfs.sys.document.FinancialSystemMaintainable#answerSplitNodeQuestion(java.lang.String) 172 */ 173 @Override 174 protected boolean answerSplitNodeQuestion(String nodeName) throws UnsupportedOperationException { 175 176 // puke if we dont know how to handle the nodeName passed in 177 if (!REQUIRES_APPROVAL_NODE.equals(nodeName)) { 178 throw new UnsupportedOperationException("answerSplitNodeQuestion('" + nodeName + "') was called, but no handler is present for that nodeName."); 179 } 180 181 // need the parent maint doc to see whether its a New or Edit, and 182 // to get the initiator 183 FinancialSystemMaintenanceDocument maintDoc = getParentMaintDoc(); 184 185 // editing a customer always requires approval, unless only Notes have changed 186 if (maintDoc.isEdit()) { 187 return !oldAndNewAreEqual(maintDoc); 188 } 189 190 // while creating a new customer, route when created by web application 191 return (maintDoc.isNew() && createdByWebApp(maintDoc)); 192 } 193 194 private boolean oldAndNewAreEqual(FinancialSystemMaintenanceDocument maintDoc) { 195 Customer oldBo, newBo; 196 CustomerAddress oldAddress, newAddress; 197 198 oldBo = (Customer) maintDoc.getOldMaintainableObject().getBusinessObject(); 199 newBo = (Customer) maintDoc.getNewMaintainableObject().getBusinessObject(); 200 201 return oldAndNewObjectIsEqual("Customer", oldBo, newBo); 202 } 203 204 private boolean oldAndNewObjectIsEqual(String objectName, Object oldObject, Object newObject) { 205 206 // if both are null, then they're the same 207 if (oldObject == null && newObject == null) { 208 return true; 209 } 210 211 // if only one is null, then they're different 212 if (oldObject == null || newObject == null) { 213 return false; 214 } 215 216 // if they're different classes, then they're different 217 if (!oldObject.getClass().getName().equals(newObject.getClass().getName())) { 218 return false; 219 } 220 221 // get the list of properties and unconverted values for readable props 222 Map<String,Object> oldProps; 223 Map<String,Object> newProps; 224 try { 225 oldProps = PropertyUtils.describe(oldObject); 226 newProps = PropertyUtils.describe(newObject); 227 } 228 catch (Exception e) { 229 throw new RuntimeException("Exception raised while trying to get a list of properties on OldCustomer.", e); 230 } 231 232 // compare old to new on all readable properties 233 Object oldValue, newValue; 234 for (String propName : oldProps.keySet()) { 235 oldValue = oldProps.get(propName); 236 newValue = newProps.get(propName); 237 238 if (!oldAndNewPropertyIsEqual(propName, oldValue, newValue)) { 239 return false; 240 } 241 } 242 243 // if we didnt find any differences, then they are the same 244 return true; 245 } 246 247 private boolean oldAndNewPropertyIsEqual(String propName, Object oldValue, Object newValue) { 248 249 // ignore anything named boNotes 250 if (BO_NOTES.equalsIgnoreCase(propName)) { 251 return true; 252 } 253 254 // if both are null, then they're the same 255 if (oldValue == null && newValue == null) { 256 return true; 257 } 258 259 // if only one is null, then they're different 260 if (oldValue == null || newValue == null) { 261 return false; 262 } 263 264 // if they're different classes, then they're different 265 if (!oldValue.getClass().getName().equals(newValue.getClass().getName())) { 266 return false; 267 } 268 269 // if they're a collection, then special handling 270 if (Collection.class.isAssignableFrom(oldValue.getClass())) { 271 Object[] oldCollection = ((Collection<Object>) oldValue).toArray(); 272 Object[] newCollection = ((Collection<Object>) newValue).toArray(); 273 274 // if they have different numbers of addresses 275 if (oldCollection.length != newCollection.length) { 276 return false; 277 } 278 279 280 for (int i = 0; i < oldCollection.length; i++) { 281 if (!oldAndNewObjectIsEqual("COLLECTION: " + propName, oldCollection[i], newCollection[i])) { 282 return false; 283 } 284 } 285 return true; 286 } 287 else { 288 boolean result = oldValue.toString().equals(newValue.toString()); 289 return result; 290 } 291 } 292 293 private FinancialSystemMaintenanceDocument getParentMaintDoc() { 294 // how I wish for the ability to directly access the parent object 295 DocumentService documentService = SpringContext.getBean(DocumentService.class); 296 FinancialSystemMaintenanceDocument maintDoc = null; 297 try { 298 maintDoc =(FinancialSystemMaintenanceDocument) documentService.getByDocumentHeaderId(this.documentNumber); 299 } 300 catch (WorkflowException e) { 301 throw new RuntimeException(e); 302 } 303 return maintDoc; 304 } 305 306 private boolean createdByWebApp(FinancialSystemMaintenanceDocument maintDoc) { 307 String initiatorPrincipalId = maintDoc.getDocumentHeader().getWorkflowDocument().getInitiatorPrincipalId(); 308 PersonService<Person> personService = SpringContext.getBean(PersonService.class); 309 Person initiatorPerson = personService.getPerson(initiatorPrincipalId); 310 return (initiatorPerson != null && !KFSConstants.SYSTEM_USER.equals(initiatorPerson.getPrincipalName())); 311 } 312 313 /** 314 * Gets the dateTimeService attribute. 315 * @return Returns the dateTimeService. 316 */ 317 public DateTimeService getDateTimeService() { 318 if (dateTimeService == null) { 319 dateTimeService = SpringContext.getBean(DateTimeService.class); 320 } 321 return dateTimeService; 322 } 323 324 }