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 }