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    }