/* * 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.vnd.document; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.lang.StringUtils; import org.kuali.kfs.sys.KFSConstants; import org.kuali.kfs.sys.context.SpringContext; import org.kuali.kfs.sys.document.FinancialSystemMaintainable; import org.kuali.kfs.vnd.VendorConstants; import org.kuali.kfs.vnd.VendorKeyConstants; import org.kuali.kfs.vnd.VendorParameterConstants; import org.kuali.kfs.vnd.VendorPropertyConstants; import org.kuali.kfs.vnd.VendorUtils; import org.kuali.kfs.vnd.businessobject.VendorDetail; import org.kuali.kfs.vnd.businessobject.VendorHeader; import org.kuali.kfs.vnd.businessobject.VendorTaxChange; import org.kuali.kfs.vnd.document.service.VendorService; import org.kuali.rice.core.api.datetime.DateTimeService; import org.kuali.rice.coreservice.framework.parameter.ParameterService; import org.kuali.rice.kew.api.WorkflowDocument; import org.kuali.rice.kim.api.identity.Person; import org.kuali.rice.kim.api.services.KimApiServiceLocator; import org.kuali.rice.kns.document.MaintenanceDocument; import org.kuali.rice.krad.bo.DocumentHeader; import org.kuali.rice.krad.bo.Note; import org.kuali.rice.krad.bo.PersistableBusinessObject; import org.kuali.rice.krad.document.Document; import org.kuali.rice.krad.maintenance.MaintenanceLock; import org.kuali.rice.krad.service.BusinessObjectService; import org.kuali.rice.krad.service.DocumentService; import org.kuali.rice.krad.service.KRADServiceLocator; import org.kuali.rice.krad.service.NoteService; import org.kuali.rice.krad.util.GlobalVariables; import org.kuali.rice.krad.util.ObjectUtils; public class VendorMaintainableImpl extends FinancialSystemMaintainable { protected static final String VENDOR_REQUIRES_APPROVAL_SPLIT_NODE = "RequiresApproval"; private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(VendorMaintainableImpl.class); /** * @see org.kuali.rice.kns.maintenance.KualiMaintainableImpl#setGenerateDefaultValues(boolean) */ @Override public void setGenerateDefaultValues(String docTypeName) { super.setGenerateDefaultValues(docTypeName); List<Note> notes = new ArrayList<Note>(); if (getBusinessObject().getObjectId() != null) { NoteService noteService = KRADServiceLocator.getNoteService(); notes = noteService.getByRemoteObjectId(this.getBusinessObject().getObjectId()); if (notes.isEmpty()) { notes.add(getNewBoNoteForAdding(VendorConstants.VendorCreateAndUpdateNotePrefixes.ADD)); } } } /** * Overrides the kuali default documents title with a Vendor-specific document title style * * @see org.kuali.rice.kns.maintenance.KualiMaintainableImpl#getDocumentTitle(org.kuali.rice.kns.document.MaintenanceDocument) */ @Override public String getDocumentTitle(MaintenanceDocument document) { String documentTitle = ""; // Check if we are choosing to override the Kuali default document title. if (SpringContext.getBean(ParameterService.class).getParameterValueAsBoolean(VendorDetail.class, VendorParameterConstants.OVERRIDE_VENDOR_DOC_TITLE)) { // We are overriding the standard with a Vendor-specific document title style. if (document.isOldBusinessObjectInDocument()) { documentTitle = "Edit Vendor - "; } else { documentTitle = "New Vendor - "; } try { Person initUser = KimApiServiceLocator.getPersonService().getPerson(document.getDocumentHeader().getWorkflowDocument().getInitiatorPrincipalId()); documentTitle += initUser.getCampusCode(); } catch (Exception e) { throw new RuntimeException("Document Initiator not found " + e.getMessage()); } VendorDetail newBo = (VendorDetail) document.getNewMaintainableObject().getBusinessObject(); if (StringUtils.isNotBlank(newBo.getVendorName())) { documentTitle += " '" + newBo.getVendorName() + "'"; } else { if (StringUtils.isNotBlank(newBo.getVendorFirstName())) { documentTitle += " '" + newBo.getVendorFirstName() + " "; if (StringUtils.isBlank(newBo.getVendorLastName())) { documentTitle += "'"; } } if (StringUtils.isNotBlank(newBo.getVendorLastName())) { if (StringUtils.isBlank(newBo.getVendorFirstName())) { documentTitle += " '"; } documentTitle += newBo.getVendorLastName() + "'"; } } if (newBo.getVendorHeader().getVendorForeignIndicator()) { documentTitle += " (F)"; } if (!newBo.isVendorParentIndicator()) { documentTitle += " (D)"; } } else { // We are using the Kuali default document title. documentTitle = super.getDocumentTitle(document); } return documentTitle; } @Override public void doRouteStatusChange(DocumentHeader header) { super.doRouteStatusChange(header); VendorDetail vendorDetail = (VendorDetail) getBusinessObject(); WorkflowDocument workflowDoc = header.getWorkflowDocument(); // This code is only executed when the final approval occurs if (workflowDoc.isProcessed()) { // This id and versionNumber null check is needed here since those fields are always null for a fresh maintenance doc. if (vendorDetail.isVendorParentIndicator() && vendorDetail.getVendorHeaderGeneratedIdentifier() != null) { VendorDetail previousParent = SpringContext.getBean(VendorService.class).getParentVendor(vendorDetail.getVendorHeaderGeneratedIdentifier()); // We'll only need to do the following if the previousParent is not the same as the current vendorDetail, because // the following lines are for vendor parent indicator changes. if (vendorDetail.getVendorDetailAssignedIdentifier() == null || previousParent.getVendorHeaderGeneratedIdentifier().intValue() != vendorDetail.getVendorHeaderGeneratedIdentifier().intValue() || previousParent.getVendorDetailAssignedIdentifier().intValue() != vendorDetail.getVendorDetailAssignedIdentifier().intValue()) { previousParent.setVendorParentIndicator(false); addNoteForParentIndicatorChange(vendorDetail, previousParent, header.getDocumentNumber()); SpringContext.getBean(BusinessObjectService.class).save(previousParent); } } // 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 // Tax Change table. if (vendorDetail.isVendorParentIndicator()) { VendorDetail oldVendorDetail = SpringContext.getBean(VendorService.class).getVendorDetail(vendorDetail.getVendorHeaderGeneratedIdentifier(), vendorDetail.getVendorDetailAssignedIdentifier()); if (ObjectUtils.isNotNull(oldVendorDetail)) { VendorHeader oldVendorHeader = oldVendorDetail.getVendorHeader(); VendorHeader newVendorHeader = vendorDetail.getVendorHeader(); if (ObjectUtils.isNotNull(oldVendorHeader)) { // Does not apply if this is a new parent vendor. String oldVendorTaxNumber = oldVendorHeader.getVendorTaxNumber(); String oldVendorTaxTypeCode = oldVendorHeader.getVendorTaxTypeCode(); String vendorTaxNumber = newVendorHeader.getVendorTaxNumber(); String vendorTaxTypeCode = newVendorHeader.getVendorTaxTypeCode(); if ((!StringUtils.equals(vendorTaxNumber, oldVendorTaxNumber)) || (!StringUtils.equals(vendorTaxTypeCode, oldVendorTaxTypeCode))) { VendorTaxChange taxChange = new VendorTaxChange(vendorDetail.getVendorHeaderGeneratedIdentifier(), SpringContext.getBean(DateTimeService.class).getCurrentTimestamp(), oldVendorTaxNumber, oldVendorTaxTypeCode, GlobalVariables.getUserSession().getPerson().getPrincipalId()); SpringContext.getBean(BusinessObjectService.class).save(taxChange); } } } } }//endif isProcessed() } /** * Add a note to the previous parent vendor to denote that parent vendor indicator change had occurred. * * @param newVendorDetail The current vendor * @param oldVendorDetail The parent vendor of the current vendor prior to this change. * @param getDocumentNumber() The document number of the document where we're attempting the parent vendor indicator change. */ private void addNoteForParentIndicatorChange(VendorDetail newVendorDetail, VendorDetail oldVendorDetail, String docNumber) { String noteText = VendorUtils.buildMessageText(VendorKeyConstants.MESSAGE_VENDOR_PARENT_TO_DIVISION, docNumber, newVendorDetail.getVendorName() + " (" + newVendorDetail.getVendorNumber() + ")"); Note newBONote = new Note(); newBONote.setNoteText(noteText); try { NoteService noteService = SpringContext.getBean(NoteService.class); newBONote = noteService.createNote(newBONote, oldVendorDetail, GlobalVariables.getUserSession().getPrincipalId()); newBONote.setNotePostedTimestampToCurrent(); noteService.save(newBONote); } catch (Exception e) { throw new RuntimeException("Caught Exception While Trying To Add Note to Vendor", e); } NoteService noteService = KRADServiceLocator.getNoteService(); List<Note> notes = noteService.getByRemoteObjectId(oldVendorDetail.getObjectId()); notes.add(newBONote); } /** * Refreshes the vendorDetail. Currently we need this mainly for refreshing the soldToVendor object after returning from the * lookup for a sold to vendor. * * @see org.kuali.rice.kns.maintenance.KualiMaintainableImpl#refresh(java.lang.String, java.util.Map, * org.kuali.rice.kns.document.MaintenanceDocument) */ @Override public void refresh(String refreshCaller, Map fieldValues, MaintenanceDocument document) { PersistableBusinessObject oldBo = document.getOldMaintainableObject().getBusinessObject(); if (ObjectUtils.isNotNull(oldBo)) { oldBo.refreshNonUpdateableReferences(); } VendorDetail newBo = (VendorDetail) document.getNewMaintainableObject().getBusinessObject(); // Here we have to temporarily save vendorHeader into a temp object, then put back // the vendorHeader into the newBo after the refresh, so that we don't lose the // values VendorHeader tempHeader = newBo.getVendorHeader(); newBo.refreshNonUpdateableReferences(); newBo.setVendorHeader(tempHeader); super.refresh(refreshCaller, fieldValues, document); } /** * Temporarily saves vendorHeader into a temp object, then put back the vendorHeader into the VendorDetail after the refresh, so * that we don't lose the values */ public void refreshBusinessObject() { VendorDetail vd = (VendorDetail) getBusinessObject(); // Here we have to temporarily save vendorHeader into a temp object, then put back // the vendorHeader into the VendorDetail after the refresh, so that we don't lose the // values VendorHeader tempHeader = vd.getVendorHeader(); vd.refreshNonUpdateableReferences(); vd.setVendorHeader(tempHeader); } /** * Checks whether the vendor has already had a vendor detail assigned id. If not, it will call the private method to set the * detail assigned id. The method will also call the vendorService to determine whether it should save the vendor header (i.e. * if this is a parent) and will save the vendor header accordingly. This is because we are not going to save vendor header * automatically along with the saving of vendor detail, so if the vendor is a parent, we have to save the vendor header * separately. Restriction-related information will be changed based on whether the Vendor Restricted Indicator was changed. If * the Tax Number or Tax Type code have changed, the fact will be recorded with a new record in the Tax Change table. Finally * the method will call the saveBusinessObject( ) of the super class to save the vendor detail. * * @see org.kuali.rice.kns.maintenance.KualiMaintainableImpl#saveBusinessObject() */ @Override public void saveBusinessObject() { VendorDetail vendorDetail = (VendorDetail) super.getBusinessObject(); VendorHeader vendorHeader = vendorDetail.getVendorHeader(); // Update miscellaneous information and save the Vendor Header if this is a parent vendor. setVendorName(vendorDetail); vendorHeader.setVendorHeaderGeneratedIdentifier(vendorDetail.getVendorHeaderGeneratedIdentifier()); if (ObjectUtils.isNull(vendorDetail.getVendorDetailAssignedIdentifier())) { setDetailAssignedId(vendorDetail); } if (vendorDetail.isVendorParentIndicator()) { SpringContext.getBean(VendorService.class).saveVendorHeader(vendorDetail); } super.saveBusinessObject(); // Populate generateid and detail assigned id in doc content after bo is persisted. try { Document document = SpringContext.getBean(DocumentService.class).getByDocumentHeaderId(getDocumentNumber()); VendorDetail vndDetail = (VendorDetail) ((MaintenanceDocument) document).getNewMaintainableObject().getBusinessObject(); if (vndDetail.getVendorHeaderGeneratedIdentifier() == null || KFSConstants.MAINTENANCE_NEWWITHEXISTING_ACTION.equals(getMaintenanceAction())) { ((MaintenanceDocument) document).getNewMaintainableObject().setBusinessObject(vendorDetail); SpringContext.getBean(DocumentService.class).saveDocument(document); } } catch (Exception e) { LOG.error("Vendor doc not saved successfully "+ e.getMessage()); } } /** * @see org.kuali.rice.kns.maintenance.KualiMaintainableImpl#processAfterEdit() */ @Override public void processAfterEdit( MaintenanceDocument document, Map<String,String[]> parameters ) { List<Note> notes = new ArrayList<Note>(); if (document.getOldMaintainableObject().getBusinessObject().getObjectId() != null) { NoteService noteService = KRADServiceLocator.getNoteService(); notes = noteService.getByRemoteObjectId(this.getBusinessObject().getObjectId()); } setVendorCreateAndUpdateNote(notes, VendorConstants.VendorCreateAndUpdateNotePrefixes.CHANGE); document.setNotes(notes); super.processAfterEdit(document, parameters); } /** * Checks whether the previous note was an "Add" with the same document number as this one * * @param notes List of exisiting notes. * @param prefix String to determine if it is a note "Add" or a note "Change" */ private void setVendorCreateAndUpdateNote(List<Note> notes, String prefix) { boolean shouldAddNote = true; if (prefix.equals(VendorConstants.VendorCreateAndUpdateNotePrefixes.CHANGE)) { // Check whether the previous note was an "Add" with the same document number as this one if (!notes.isEmpty()) { Note previousNote = notes.get(notes.size() - 1 ); if (previousNote.getNoteText().contains(getDocumentNumber())) { shouldAddNote = false; } } } if (shouldAddNote) { notes.add(getNewBoNoteForAdding(prefix)); } } /** * creates a new bo note and sets the timestamp. * * @return a newly created note */ protected Note getNewBoNoteForAdding(String prefix) { Note newBoNote = new Note(); newBoNote.setNoteText(prefix + " vendor document ID " + getDocumentNumber()); newBoNote.setNotePostedTimestampToCurrent(); try { newBoNote = SpringContext.getBean(NoteService.class).createNote(newBoNote, this.getBusinessObject(), GlobalVariables.getUserSession().getPrincipalId()); } catch (Exception e) { throw new RuntimeException("Caught Exception While Trying To Add Note to Vendor", e); } return newBoNote; } /** * Concatenates the vendorLastName and a delimiter and the vendorFirstName fields into vendorName field of the vendorDetail * object. * * @param vendorDetail VendorDetail The vendor whose name field we are trying to assign */ private void setVendorName(VendorDetail vendorDetail) { if (vendorDetail.isVendorFirstLastNameIndicator()) { vendorDetail.setVendorName(vendorDetail.getVendorLastName() + VendorConstants.NAME_DELIM + vendorDetail.getVendorFirstName()); } } /** * If the vendorFirstLastNameIndicator is true, this method will set the vendor first name and vendor last name fields from the * vendorName field, then set the vendorName field to null. Then it sets the businessObject of this maintainable to the * VendorDetail object that contains our modification to the name fields. * * @see org.kuali.rice.kns.maintenance.Maintainable#saveBusinessObject() */ @Override public void setBusinessObject(PersistableBusinessObject bo) { VendorDetail originalBo = (VendorDetail) bo; String vendorName = originalBo.getVendorName(); if (originalBo.isVendorFirstLastNameIndicator() && ObjectUtils.isNotNull(vendorName)) { int start = vendorName.indexOf(VendorConstants.NAME_DELIM); if (start >= 0) { String lastName = vendorName.substring(0, start); String firstName = new String(); if (start + VendorConstants.NAME_DELIM.length() <= vendorName.length()) { firstName = vendorName.substring(start + VendorConstants.NAME_DELIM.length(), vendorName.length()); } originalBo.setVendorFirstName((ObjectUtils.isNotNull(firstName) ? firstName.trim() : firstName)); originalBo.setVendorLastName((ObjectUtils.isNotNull(lastName) ? lastName.trim() : lastName)); originalBo.setVendorName(null); } } super.setBusinessObject(originalBo); } /** * 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 * 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 * for the count of vendor details in the database whose vendor header id match with the vendor header id of this new division, * 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 * id and detail id as the count has existed. If a vendor with such criteria exists, this method will increment the count * 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 * boolean flag to stop the loop, because we have already found the valid detail assigned id that we were looking for * * @param vendorDetail VendorDetail The vendor whose detail assigned id we're trying to assign. */ private void setDetailAssignedId(VendorDetail vendorDetail) { // If this is a new parent, let's set the detail id to 0. if (ObjectUtils.isNull(vendorDetail.getVendorHeaderGeneratedIdentifier())) { vendorDetail.setVendorDetailAssignedIdentifier(new Integer(0)); } else { // Try to get the count of all the vendor whose header id is the same as this header id. Map criterias = new HashMap(); criterias.put(VendorPropertyConstants.VENDOR_HEADER_GENERATED_ID, vendorDetail.getVendorHeaderGeneratedIdentifier()); BusinessObjectService boService = SpringContext.getBean(BusinessObjectService.class); int count = boService.countMatching(VendorDetail.class, criterias); boolean validId = false; while (!validId) { criterias.put(VendorPropertyConstants.VENDOR_DETAIL_ASSIGNED_ID, count); int result = boService.countMatching(VendorDetail.class, criterias); if (result > 0) { // increment the detail id by 1 count++; } else { // count is a validId, so we'll use count as our vendor detail assigned id validId = true; vendorDetail.setVendorDetailAssignedIdentifier(new Integer(count)); } } } } /** * Returns the locking representation of the vendor. If the vendor detail id is not null, call the super class * implementation of generateMaintenanceLocks which will set the locking key to be the header and detail ids. However, if the * detail id is null, that means this is a new vendor (parent or division) and we should ignore locking. * * @see org.kuali.rice.kns.maintenance.Maintainable#generateMaintenanceLocks() */ @Override public List<MaintenanceLock> generateMaintenanceLocks() { if (ObjectUtils.isNotNull(((VendorDetail) getBusinessObject()).getVendorDetailAssignedIdentifier())) { return super.generateMaintenanceLocks(); } else { return new ArrayList(); } } /** * Create a new division vendor if the user clicks on the "Create a new division" link. By default, the vendorParentIndicator is * 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 * the vendorParentIndicator to false in this case. * * @see org.kuali.rice.kns.maintenance.Maintainable#setupNewFromExisting() */ @Override public void setupNewFromExisting( MaintenanceDocument document, Map<String,String[]> parameters ) { super.setupNewFromExisting(document, parameters); ((VendorDetail) super.getBusinessObject()).setVendorParentIndicator(false); ((VendorDetail) super.getBusinessObject()).setActiveIndicator(true); List<Note> notes = new ArrayList<Note>(); if (getBusinessObject().getObjectId() != null) { NoteService noteService = KRADServiceLocator.getNoteService(); notes = noteService.getByRemoteObjectId(this.getBusinessObject().getObjectId()); } setVendorCreateAndUpdateNote(notes, VendorConstants.VendorCreateAndUpdateNotePrefixes.ADD); document.setNotes(notes); } /** * @see org.kuali.rice.kns.maintenance.KualiMaintainableImpl#isRelationshipRefreshable(java.lang.Class, java.lang.String) */ @Override protected boolean isRelationshipRefreshable(Class boClass, String relationshipName) { if (VendorDetail.class.isAssignableFrom(boClass) && VendorConstants.VENDOR_HEADER_ATTR.equals(relationshipName)) { return false; } return super.isRelationshipRefreshable(boClass, relationshipName); } /** * @see org.kuali.kfs.sys.document.FinancialSystemMaintainable#answerSplitNodeQuestion(java.lang.String) */ @Override protected boolean answerSplitNodeQuestion(String nodeName) throws UnsupportedOperationException { if (nodeName.equals(VENDOR_REQUIRES_APPROVAL_SPLIT_NODE)) { return SpringContext.getBean(VendorService.class).shouldVendorRouteForApproval(getDocumentNumber()); } return super.answerSplitNodeQuestion(nodeName); } }