/* * The Kuali Financial System, a comprehensive financial management system for higher education. * * Copyright 2005-2014 The Kuali Foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.kuali.kfs.module.ar.document; import java.sql.Date; import java.util.Collection; import java.util.List; import java.util.Map; import org.apache.commons.beanutils.PropertyUtils; import org.apache.commons.lang.StringUtils; import org.kuali.kfs.module.ar.ArKeyConstants; import org.kuali.kfs.module.ar.ArPropertyConstants; import org.kuali.kfs.module.ar.businessobject.ARCollector; import org.kuali.kfs.module.ar.businessobject.Customer; import org.kuali.kfs.module.ar.businessobject.CustomerAddress; import org.kuali.kfs.module.ar.service.CustomerViewService; import org.kuali.kfs.sys.KFSConstants; import org.kuali.kfs.sys.context.SpringContext; import org.kuali.kfs.sys.document.FinancialSystemMaintainable; import org.kuali.kfs.sys.document.FinancialSystemMaintenanceDocument; import org.kuali.rice.core.api.datetime.DateTimeService; import org.kuali.rice.kew.api.exception.WorkflowException; import org.kuali.rice.kim.api.identity.Person; import org.kuali.rice.kim.api.identity.PersonService; import org.kuali.rice.kim.api.identity.principal.Principal; import org.kuali.rice.kim.api.services.KimApiServiceLocator; import org.kuali.rice.kns.document.MaintenanceDocument; import org.kuali.rice.kns.maintenance.Maintainable; import org.kuali.rice.kns.web.ui.Section; import org.kuali.rice.krad.bo.DocumentHeader; import org.kuali.rice.krad.bo.PersistableBusinessObject; import org.kuali.rice.krad.service.DocumentService; import org.kuali.rice.krad.util.KRADConstants; import org.kuali.rice.krad.util.ObjectUtils; public class CustomerMaintenableImpl extends FinancialSystemMaintainable { private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(CustomerMaintenableImpl.class); private static final String REQUIRES_APPROVAL_NODE = "RequiresApproval"; private static final String BO_NOTES = "boNotes"; private transient DateTimeService dateTimeService; private static volatile CustomerViewService customerViewService; /** * overridden to hide CGB fields/sections if CGB is disabled * * @see org.kuali.rice.kns.maintenance.KualiMaintainableImpl#getSections(org.kuali.rice.kns.document.MaintenanceDocument, org.kuali.rice.kns.maintenance.Maintainable) */ @Override public List getSections(MaintenanceDocument document, Maintainable oldMaintainable) { List<Section> sections = super.getSections(document, oldMaintainable); sections = getCustomerViewService().getSections(sections); return sections; } @Override @SuppressWarnings("unchecked") public void processAfterPost(MaintenanceDocument document, Map<String, String[]> parameters) { super.processAfterPost(document, parameters); // when we create new customer set the customerRecordAddDate to current date if (getMaintenanceAction().equalsIgnoreCase(KRADConstants.MAINTENANCE_NEW_ACTION)) { Customer oldCustomer = (Customer) document.getOldMaintainableObject().getBusinessObject(); Customer newCustomer = (Customer) document.getNewMaintainableObject().getBusinessObject(); Date currentDate = getDateTimeService().getCurrentSqlDate(); newCustomer.setCustomerRecordAddDate(currentDate); newCustomer.setCustomerLastActivityDate(currentDate); } } @Override public void doRouteStatusChange(DocumentHeader documentHeader) { super.doRouteStatusChange(documentHeader); // if new customer was created => dates have been already updated if (getMaintenanceAction().equalsIgnoreCase(KRADConstants.MAINTENANCE_NEW_ACTION)) { return; } if (documentHeader.getWorkflowDocument().isProcessed()) { DocumentService documentService = SpringContext.getBean(DocumentService.class); try { MaintenanceDocument document = (MaintenanceDocument) documentService.getByDocumentHeaderId(documentHeader.getDocumentNumber()); Customer newCustomer = (Customer) document.getNewMaintainableObject().getBusinessObject(); Customer oldCustomer = (Customer) document.getOldMaintainableObject().getBusinessObject(); updateDates(oldCustomer, newCustomer); } catch (WorkflowException e) { LOG.error("caught exception while handling handleRouteStatusChange -> documentService.getByDocumentHeaderId(" + documentHeader.getDocumentNumber() + "). ", e); } } } // Update dates (last activity date and address change date) private void updateDates(Customer oldCustomer, Customer newCustomer) { Date currentDate = getDateTimeService().getCurrentSqlDate(); List oldAddresses = oldCustomer.getCustomerAddresses(); List newAddresses = newCustomer.getCustomerAddresses(); boolean addressChangeFlag = false; // if new address was added or one of the old addresses was changed/deleted, set customerAddressChangeDate to the current date if (oldAddresses != null && newAddresses != null) { if (oldAddresses.size() != newAddresses.size()) { newCustomer.setCustomerAddressChangeDate(currentDate); newCustomer.setCustomerLastActivityDate(currentDate); addressChangeFlag = true; } else { for (int i = 0; i < oldAddresses.size(); i++) { CustomerAddress oldAddress = (CustomerAddress) oldAddresses.get(i); CustomerAddress newAddress = (CustomerAddress) newAddresses.get(i); if (oldAddress.compareTo(newAddress) != 0) { newCustomer.setCustomerAddressChangeDate(currentDate); newCustomer.setCustomerLastActivityDate(currentDate); addressChangeFlag = true; break; } } } } // if non address related change if (!addressChangeFlag && !oldCustomer.equals(newCustomer)) { newCustomer.setCustomerLastActivityDate(currentDate); } } @Override public PersistableBusinessObject initNewCollectionLine(String collectionName) { PersistableBusinessObject businessObject = super.initNewCollectionLine(collectionName); Customer customer = (Customer) this.businessObject; if (collectionName.equalsIgnoreCase(ArPropertyConstants.CustomerFields.CUSTOMER_TAB_ADDRESSES)) { CustomerAddress customerAddress = (CustomerAddress) businessObject; // set default address name to customer name customerAddress.setCustomerAddressName(customer.getCustomerName()); if (KRADConstants.MAINTENANCE_NEW_ACTION.equalsIgnoreCase(getMaintenanceAction())) { boolean hasPrimaryAddress = false; for (CustomerAddress tempAddress : customer.getCustomerAddresses()) { if (ArKeyConstants.CustomerConstants.CUSTOMER_ADDRESS_TYPE_CODE_PRIMARY.equalsIgnoreCase(tempAddress.getCustomerAddressTypeCode())) { hasPrimaryAddress = true; break; } } // if maintenance action is NEW and customer already has a primary address set default value for address type code // to "Alternate" if (hasPrimaryAddress) { customerAddress.setCustomerAddressTypeCode(ArKeyConstants.CustomerConstants.CUSTOMER_ADDRESS_TYPE_CODE_ALTERNATE); } // otherwise set default value for address type code to "Primary" else { customerAddress.setCustomerAddressTypeCode(ArKeyConstants.CustomerConstants.CUSTOMER_ADDRESS_TYPE_CODE_PRIMARY); } } // if maintenance action is EDIT or COPY set default value for address type code to "Alternate" if (KRADConstants.MAINTENANCE_EDIT_ACTION.equalsIgnoreCase(getMaintenanceAction()) || KRADConstants.MAINTENANCE_COPY_ACTION.equalsIgnoreCase(getMaintenanceAction())) { customerAddress.setCustomerAddressTypeCode(ArKeyConstants.CustomerConstants.CUSTOMER_ADDRESS_TYPE_CODE_ALTERNATE); } } return businessObject; } /** * Answers true for 2 conditions... * <li>a)New customer created by non-batch (web) user</li> * <li>b)Any edit to an existing customer record</li> * * @see org.kuali.kfs.sys.document.FinancialSystemMaintainable#answerSplitNodeQuestion(java.lang.String) */ @Override protected boolean answerSplitNodeQuestion(String nodeName) throws UnsupportedOperationException { // puke if we dont know how to handle the nodeName passed in if (!REQUIRES_APPROVAL_NODE.equals(nodeName)) { throw new UnsupportedOperationException("answerSplitNodeQuestion('" + nodeName + "') was called, but no handler is present for that nodeName."); } // need the parent maint doc to see whether its a New or Edit, and // to get the initiator FinancialSystemMaintenanceDocument maintDoc = getParentMaintDoc(); // editing a customer always requires approval, unless only Notes have changed if (maintDoc.isEdit()) { return !oldAndNewAreEqual(maintDoc); } // while creating a new customer, route when created by web application return (maintDoc.isNew() && createdByWebApp(maintDoc)); } private boolean oldAndNewAreEqual(FinancialSystemMaintenanceDocument maintDoc) { Customer oldBo, newBo; CustomerAddress oldAddress, newAddress; oldBo = (Customer) maintDoc.getOldMaintainableObject().getBusinessObject(); newBo = (Customer) maintDoc.getNewMaintainableObject().getBusinessObject(); return oldAndNewObjectIsEqual("Customer", oldBo, newBo); } private boolean oldAndNewObjectIsEqual(String objectName, Object oldObject, Object newObject) { // if both are null, then they're the same if (oldObject == null && newObject == null) { return true; } // if only one is null, then they're different if (oldObject == null || newObject == null) { return false; } // if they're different classes, then they're different if (!oldObject.getClass().getName().equals(newObject.getClass().getName())) { return false; } // get the list of properties and unconverted values for readable props Map<String,Object> oldProps; Map<String,Object> newProps; try { oldProps = PropertyUtils.describe(oldObject); newProps = PropertyUtils.describe(newObject); } catch (Exception e) { throw new RuntimeException("Exception raised while trying to get a list of properties on OldCustomer.", e); } // compare old to new on all readable properties Object oldValue, newValue; for (String propName : oldProps.keySet()) { oldValue = oldProps.get(propName); newValue = newProps.get(propName); if (!oldAndNewPropertyIsEqual(propName, oldValue, newValue)) { return false; } } // if we didnt find any differences, then they are the same return true; } private boolean oldAndNewPropertyIsEqual(String propName, Object oldValue, Object newValue) { // ignore anything named boNotes if (BO_NOTES.equalsIgnoreCase(propName)) { return true; } // if both are null, then they're the same if (oldValue == null && newValue == null) { return true; } // if only one is null, then they're different if (oldValue == null || newValue == null) { return false; } // if they're different classes, then they're different if (!oldValue.getClass().getName().equals(newValue.getClass().getName())) { return false; } // if they're a collection, then special handling if (Collection.class.isAssignableFrom(oldValue.getClass())) { Object[] oldCollection = ((Collection<Object>) oldValue).toArray(); Object[] newCollection = ((Collection<Object>) newValue).toArray(); // if they have different numbers of addresses if (oldCollection.length != newCollection.length) { return false; } for (int i = 0; i < oldCollection.length; i++) { if (!oldAndNewObjectIsEqual("COLLECTION: " + propName, oldCollection[i], newCollection[i])) { return false; } } return true; } else { boolean result = oldValue.toString().equals(newValue.toString()); return result; } } private FinancialSystemMaintenanceDocument getParentMaintDoc() { // how I wish for the ability to directly access the parent object DocumentService documentService = SpringContext.getBean(DocumentService.class); FinancialSystemMaintenanceDocument maintDoc = null; try { maintDoc =(FinancialSystemMaintenanceDocument) documentService.getByDocumentHeaderId(getDocumentNumber()); } catch (WorkflowException e) { throw new RuntimeException(e); } return maintDoc; } private boolean createdByWebApp(FinancialSystemMaintenanceDocument maintDoc) { String initiatorPrincipalId = maintDoc.getDocumentHeader().getWorkflowDocument().getInitiatorPrincipalId(); Principal initiatorPrincipal = KimApiServiceLocator.getIdentityService().getPrincipal(initiatorPrincipalId); return (initiatorPrincipal != null && !KFSConstants.SYSTEM_USER.equals(initiatorPrincipal.getPrincipalName())); } /** * Gets the dateTimeService attribute. * @return Returns the dateTimeService. */ public DateTimeService getDateTimeService() { if (dateTimeService == null) { dateTimeService = SpringContext.getBean(DateTimeService.class); } return dateTimeService; } /** * This method is called before the object is added in collection. * * @see org.kuali.rice.kns.maintenance.KualiMaintainableImpl#addNewLineToCollection(java.lang.String) */ @Override public void addNewLineToCollection(String collectionName) { refreshCustomer(false); super.addNewLineToCollection(collectionName); } private void refreshCustomer(boolean refreshFromLookup) { Customer customer = getCustomer(); customer.refreshNonUpdateableReferences(); } private void refreshWithSecondaryKey(ARCollector arCollector) { Person collector = arCollector.getCollector(); if (ObjectUtils.isNotNull(collector)) { String secondaryKey = collector.getPrincipalName(); if (StringUtils.isNotBlank(secondaryKey)) { Person dir = SpringContext.getBean(PersonService.class).getPersonByPrincipalName(secondaryKey); arCollector.setPrincipalId(dir == null ? null : dir.getPrincipalId()); } if (StringUtils.isNotBlank(arCollector.getPrincipalId())) { Person person = SpringContext.getBean(PersonService.class).getPerson(arCollector.getPrincipalId()); if (person != null) { ((PersistableBusinessObject) arCollector).refreshNonUpdateableReferences(); } } } } /** * Gets the underlying Customer. * * @return */ public Customer getCustomer() { return (Customer) getBusinessObject(); } /** * @param collection */ private static void refreshNonUpdateableReferences(Collection<? extends PersistableBusinessObject> collection) { for (PersistableBusinessObject item : collection) { item.refreshNonUpdateableReferences(); } } @Override public void processAfterRetrieve() { refreshCustomer(false); super.processAfterRetrieve(); } /** * Overriding to default fields on the document for new documents * * @see org.kuali.rice.kns.maintenance.KualiMaintainableImpl#processAfterEdit(org.kuali.rice.kns.document.MaintenanceDocument, * java.util.Map) */ @Override public void processAfterEdit(MaintenanceDocument document, Map<String, String[]> parameters) { super.processAfterEdit(document, parameters); // Default the invoice template field on the addresses tab defaultInvoiceTemplate(getCustomer(), document); } /** * Overriding to default fields on the document for copied documents * * @see org.kuali.rice.kns.maintenance.KualiMaintainableImpl#processAfterEdit(org.kuali.rice.kns.document.MaintenanceDocument, * java.util.Map) */ @Override public void processAfterCopy(MaintenanceDocument document, Map<String, String[]> parameters) { super.processAfterEdit(document, parameters); // Default the invoice template field on the addresses tab defaultInvoiceTemplate(getCustomer(), document); } /** * Defaults the invoice template field for new Agency Addresses on the given Agency maintenance document * * @param agency * @param document */ private void defaultInvoiceTemplate(Customer customer, MaintenanceDocument document) { // Default Invoice Template for new Agency Addresses if (ObjectUtils.isNotNull(customer)) { CustomerAddress newCustomerAddress = (CustomerAddress) document.getNewMaintainableObject().getNewCollectionLine(ArPropertyConstants.CustomerFields.CUSTOMER_TAB_ADDRESSES); newCustomerAddress.setCustomerInvoiceTemplateCode(customer.getCustomerInvoiceTemplateCode()); } } /** * Overridden to set the default values on the Agency document. * * @see org.kuali.rice.kns.maintenance.KualiMaintainableImpl#processAfterNew(org.kuali.rice.kns.document.MaintenanceDocument, * java.util.Map) */ @Override public void processAfterNew(MaintenanceDocument document, Map<String, String[]> parameters) { super.processAfterNew(document, parameters); // Default the invoice template field on the addresses tab defaultInvoiceTemplate(getCustomer(), document); } public static CustomerViewService getCustomerViewService() { if (customerViewService == null) { customerViewService = SpringContext.getBean(CustomerViewService.class); } return customerViewService; } }