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.vnd.document;
017    
018    import java.util.ArrayList;
019    import java.util.HashMap;
020    import java.util.List;
021    import java.util.Map;
022    
023    import org.apache.commons.lang.StringUtils;
024    import org.kuali.kfs.sys.context.SpringContext;
025    import org.kuali.kfs.sys.document.FinancialSystemMaintainable;
026    import org.kuali.kfs.vnd.VendorConstants;
027    import org.kuali.kfs.vnd.VendorKeyConstants;
028    import org.kuali.kfs.vnd.VendorParameterConstants;
029    import org.kuali.kfs.vnd.VendorPropertyConstants;
030    import org.kuali.kfs.vnd.VendorUtils;
031    import org.kuali.kfs.vnd.businessobject.VendorDetail;
032    import org.kuali.kfs.vnd.businessobject.VendorHeader;
033    import org.kuali.kfs.vnd.businessobject.VendorTaxChange;
034    import org.kuali.kfs.vnd.document.service.VendorService;
035    import org.kuali.rice.kim.bo.Person;
036    import org.kuali.rice.kns.bo.DocumentHeader;
037    import org.kuali.rice.kns.bo.Note;
038    import org.kuali.rice.kns.bo.PersistableBusinessObject;
039    import org.kuali.rice.kns.document.MaintenanceDocument;
040    import org.kuali.rice.kns.document.MaintenanceLock;
041    import org.kuali.rice.kns.service.BusinessObjectService;
042    import org.kuali.rice.kns.service.DateTimeService;
043    import org.kuali.rice.kns.service.NoteService;
044    import org.kuali.rice.kns.service.ParameterService;
045    import org.kuali.rice.kns.util.GlobalVariables;
046    import org.kuali.rice.kns.util.ObjectUtils;
047    import org.kuali.rice.kns.workflow.service.KualiWorkflowDocument;
048    
049    public class VendorMaintainableImpl extends FinancialSystemMaintainable {
050        private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(VendorMaintainableImpl.class);
051    
052        /**
053         * @see org.kuali.rice.kns.maintenance.KualiMaintainableImpl#setGenerateDefaultValues(boolean)
054         */
055            @Override
056        public void setGenerateDefaultValues(String docTypeName) {
057            super.setGenerateDefaultValues(docTypeName);
058            if (this.getBusinessObject().getBoNotes().isEmpty()) {
059                setVendorCreateAndUpdateNote(VendorConstants.VendorCreateAndUpdateNotePrefixes.ADD);
060            }
061        }
062    
063        /**
064         * Overrides the kuali default documents title with a Vendor-specific document title style
065         * 
066         * @see org.kuali.rice.kns.maintenance.KualiMaintainableImpl#getDocumentTitle(org.kuali.rice.kns.document.MaintenanceDocument)
067         */
068        @Override
069        public String getDocumentTitle(MaintenanceDocument document) {
070            String documentTitle = "";
071            // Check if we are choosing to override the Kuali default document title.
072            if (SpringContext.getBean(ParameterService.class).getIndicatorParameter(VendorDetail.class, VendorParameterConstants.OVERRIDE_VENDOR_DOC_TITLE)) {
073                // We are overriding the standard with a Vendor-specific document title style.
074                if (document.isOldBusinessObjectInDocument()) {
075                    documentTitle = "Edit Vendor - ";
076                }
077                else {
078                    documentTitle = "New Vendor - ";
079                }
080    
081                try {
082                    Person initUser = SpringContext.getBean(org.kuali.rice.kim.service.PersonService.class).getPersonByPrincipalName(document.getDocumentHeader().getWorkflowDocument().getInitiatorNetworkId());
083                    documentTitle += initUser.getCampusCode();
084                }
085                catch (Exception e) {
086                    throw new RuntimeException("Document Initiator not found " + e.getMessage());
087                }
088    
089                VendorDetail newBo = (VendorDetail) document.getNewMaintainableObject().getBusinessObject();
090    
091                if (StringUtils.isNotBlank(newBo.getVendorName())) {
092                    documentTitle += " '" + newBo.getVendorName() + "'";
093                } 
094                else {            
095                    if (StringUtils.isNotBlank(newBo.getVendorFirstName())) {
096                        documentTitle += " '" + newBo.getVendorFirstName() + " ";
097                        if (StringUtils.isBlank(newBo.getVendorLastName())) {
098                            documentTitle += "'";
099                        }
100                    }
101                    
102                    if (StringUtils.isNotBlank(newBo.getVendorLastName())) {
103                        if (StringUtils.isBlank(newBo.getVendorFirstName())) {
104                            documentTitle += " '";
105                        }
106                        documentTitle += newBo.getVendorLastName() + "'";
107                    }
108                }
109    
110                if (newBo.getVendorHeader().getVendorForeignIndicator()) {
111                    documentTitle += " (F)";
112                }
113    
114                if (!newBo.isVendorParentIndicator()) {
115                    documentTitle += " (D)";
116                }
117            }
118            else { // We are using the Kuali default document title.
119                documentTitle = super.getDocumentTitle(document);
120            }
121            return documentTitle;
122        }
123    
124        @Override
125        public void doRouteStatusChange(DocumentHeader header) {
126            super.doRouteStatusChange(header);
127            VendorDetail vendorDetail = (VendorDetail) getBusinessObject();
128            KualiWorkflowDocument workflowDoc = header.getWorkflowDocument();
129    
130            // This code is only executed when the final approval occurs
131            if (workflowDoc.stateIsProcessed()) {
132                // This id and versionNumber null check is needed here since those fields are always null for a fresh maintenance doc.
133                if (vendorDetail.isVendorParentIndicator() && vendorDetail.getVendorHeaderGeneratedIdentifier() != null) { 
134                    VendorDetail previousParent = SpringContext.getBean(VendorService.class).getParentVendor(vendorDetail.getVendorHeaderGeneratedIdentifier());
135                    // We'll only need to do the following if the previousParent is not the same as the current vendorDetail, because
136                    // the following lines are for vendor parent indicator changes.
137                    if (vendorDetail.getVendorDetailAssignedIdentifier() == null || 
138                            previousParent.getVendorHeaderGeneratedIdentifier().intValue() != vendorDetail.getVendorHeaderGeneratedIdentifier().intValue() || 
139                            previousParent.getVendorDetailAssignedIdentifier().intValue() != vendorDetail.getVendorDetailAssignedIdentifier().intValue()) {
140                        previousParent.setVendorParentIndicator(false);
141                        addNoteForParentIndicatorChange(vendorDetail, previousParent, header.getDocumentNumber());
142                        SpringContext.getBean(BusinessObjectService.class).save(previousParent);
143                    }
144                }
145    
146                // 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
147                // Tax Change table.
148                if (vendorDetail.isVendorParentIndicator()) {
149                    VendorDetail oldVendorDetail = SpringContext.getBean(VendorService.class).getVendorDetail(vendorDetail.getVendorHeaderGeneratedIdentifier(), vendorDetail.getVendorDetailAssignedIdentifier());
150                    if (ObjectUtils.isNotNull(oldVendorDetail)) {
151                        VendorHeader oldVendorHeader = oldVendorDetail.getVendorHeader();
152                        VendorHeader newVendorHeader = vendorDetail.getVendorHeader();
153    
154                        if (ObjectUtils.isNotNull(oldVendorHeader)) { // Does not apply if this is a new parent vendor.
155                            String oldVendorTaxNumber = oldVendorHeader.getVendorTaxNumber();
156                            String oldVendorTaxTypeCode = oldVendorHeader.getVendorTaxTypeCode();
157    
158                            String vendorTaxNumber = newVendorHeader.getVendorTaxNumber();
159                            String vendorTaxTypeCode = newVendorHeader.getVendorTaxTypeCode();
160    
161                            if ((!StringUtils.equals(vendorTaxNumber, oldVendorTaxNumber)) || (!StringUtils.equals(vendorTaxTypeCode, oldVendorTaxTypeCode))) {
162                                VendorTaxChange taxChange = new VendorTaxChange(vendorDetail.getVendorHeaderGeneratedIdentifier(), SpringContext.getBean(DateTimeService.class).getCurrentTimestamp(), oldVendorTaxNumber, oldVendorTaxTypeCode, GlobalVariables.getUserSession().getPerson().getPrincipalId());
163                                SpringContext.getBean(BusinessObjectService.class).save(taxChange);
164                            }
165                        }
166                    }
167                }
168    
169            }//endif stateIsProcessed()
170        }
171        
172        /**
173         * Add a note to the previous parent vendor to denote that parent vendor indicator change had occurred.
174         * 
175         * @param newVendorDetail The current vendor
176         * @param oldVendorDetail The parent vendor of the current vendor prior to this change.
177         * @param documentNumber The document number of the document where we're attempting the parent vendor indicator change.
178         */
179        private void addNoteForParentIndicatorChange(VendorDetail newVendorDetail, VendorDetail oldVendorDetail, String documentNumber) {
180            String noteText = VendorUtils.buildMessageText(VendorKeyConstants.MESSAGE_VENDOR_PARENT_TO_DIVISION, documentNumber, newVendorDetail.getVendorName() + " (" + newVendorDetail.getVendorNumber() + ")");   
181            Note newBONote = new Note();
182            newBONote.setNoteText(noteText);
183            try {
184                NoteService noteService = SpringContext.getBean(NoteService.class);
185                newBONote = noteService.createNote(newBONote, oldVendorDetail);
186                noteService.save(newBONote);
187            }
188            catch (Exception e) {
189                throw new RuntimeException("Caught Exception While Trying To Add Note to Vendor", e);
190            }
191            oldVendorDetail.getBoNotes().add(newBONote);
192            
193        }
194        
195        /**
196         * Refreshes the vendorDetail. Currently we need this mainly for refreshing the soldToVendor object after returning from the
197         * lookup for a sold to vendor.
198         * 
199         * @see org.kuali.rice.kns.maintenance.KualiMaintainableImpl#refresh(java.lang.String, java.util.Map,
200         *      org.kuali.rice.kns.document.MaintenanceDocument)
201         */
202        @Override
203        public void refresh(String refreshCaller, Map fieldValues, MaintenanceDocument document) {
204            PersistableBusinessObject oldBo = (PersistableBusinessObject) document.getOldMaintainableObject().getBusinessObject();
205            if (ObjectUtils.isNotNull(oldBo)) {
206                oldBo.refreshNonUpdateableReferences();
207            }
208            VendorDetail newBo = (VendorDetail) document.getNewMaintainableObject().getBusinessObject();
209            // Here we have to temporarily save vendorHeader into a temp object, then put back
210            // the vendorHeader into the newBo after the refresh, so that we don't lose the
211            // values
212            VendorHeader tempHeader = newBo.getVendorHeader();
213            newBo.refreshNonUpdateableReferences();
214            newBo.setVendorHeader(tempHeader);
215            super.refresh(refreshCaller, fieldValues, document);
216        }
217    
218        /**
219         * Temporarily saves vendorHeader into a temp object, then put back the vendorHeader into the VendorDetail after the refresh, so
220         * that we don't lose the values
221         */
222        public void refreshBusinessObject() {
223            VendorDetail vd = (VendorDetail) getBusinessObject();
224            // Here we have to temporarily save vendorHeader into a temp object, then put back
225            // the vendorHeader into the VendorDetail after the refresh, so that we don't lose the
226            // values
227            VendorHeader tempHeader = vd.getVendorHeader();
228            vd.refreshNonUpdateableReferences();
229            vd.setVendorHeader(tempHeader);
230        }
231    
232    
233        /**
234         * Checks whether the vendor has already had a vendor detail assigned id. If not, it will call the private method to set the
235         * detail assigned id. The method will also call the vendorService to determine whether it should save the vendor header (i.e.
236         * if this is a parent) and will save the vendor header accordingly. This is because we are not going to save vendor header
237         * automatically along with the saving of vendor detail, so if the vendor is a parent, we have to save the vendor header
238         * separately. Restriction-related information will be changed based on whether the Vendor Restricted Indicator was changed. If
239         * the Tax Number or Tax Type code have changed, the fact will be recorded with a new record in the Tax Change table. Finally
240         * the method will call the saveBusinessObject( ) of the super class to save the vendor detail.
241         * 
242         * @see org.kuali.rice.kns.maintenance.KualiMaintainableImpl#saveBusinessObject()
243         */
244        @Override
245        public void saveBusinessObject() {
246            VendorDetail vendorDetail = (VendorDetail) super.getBusinessObject();
247            VendorHeader vendorHeader = vendorDetail.getVendorHeader();
248    
249            // Update miscellaneous information and save the Vendor Header if this is a parent vendor.
250            setVendorName(vendorDetail);
251            vendorHeader.setVendorHeaderGeneratedIdentifier(vendorDetail.getVendorHeaderGeneratedIdentifier());
252            if (ObjectUtils.isNull(vendorDetail.getVendorDetailAssignedIdentifier())) {
253                setDetailAssignedId(vendorDetail);
254            }
255            if (vendorDetail.isVendorParentIndicator()) {
256                SpringContext.getBean(VendorService.class).saveVendorHeader(vendorDetail);
257            }
258            super.saveBusinessObject();
259        }
260    
261        /**
262         * @see org.kuali.rice.kns.maintenance.KualiMaintainableImpl#processAfterEdit()
263         */
264        @Override
265        public void processAfterEdit( MaintenanceDocument document, Map<String,String[]> parameters ) {
266            setVendorCreateAndUpdateNote(VendorConstants.VendorCreateAndUpdateNotePrefixes.CHANGE);
267            super.processAfterEdit(document, parameters);
268        }
269    
270        /**
271         * Checks whether the previous note was an "Add" with the same document number as this one
272         * 
273         * @param prefix String to determine if it is a note "Add" or a note "Change"
274         */
275        private void setVendorCreateAndUpdateNote(String prefix) {
276            boolean shouldAddNote = true;
277            if (prefix.equals(VendorConstants.VendorCreateAndUpdateNotePrefixes.CHANGE)) {
278                // Check whether the previous note was an "Add" with the same document number as this one
279                if (!this.getBusinessObject().getBoNotes().isEmpty()) {
280                    Note previousNote = this.getBusinessObject().getBoNote(this.getBusinessObject().getBoNotes().size() - 1);
281                    if (previousNote.getNoteText().contains(this.documentNumber)) {
282                        shouldAddNote = false;
283                    }
284                }
285            }
286            if (shouldAddNote) {
287                Note newBONote = new Note();
288                newBONote.setNoteText(prefix + " vendor document ID " + this.documentNumber);
289                try {
290                    newBONote = SpringContext.getBean(NoteService.class).createNote(newBONote, this.getBusinessObject());
291                }
292                catch (Exception e) {
293                    throw new RuntimeException("Caught Exception While Trying To Add Note to Vendor", e);
294                }
295                this.getBusinessObject().getBoNotes().add(newBONote);
296            }
297        }
298    
299        /**
300         * Concatenates the vendorLastName and a delimiter and the vendorFirstName fields into vendorName field of the vendorDetail
301         * object.
302         * 
303         * @param vendorDetail VendorDetail The vendor whose name field we are trying to assign
304         */
305        private void setVendorName(VendorDetail vendorDetail) {
306            if (vendorDetail.isVendorFirstLastNameIndicator()) {
307                vendorDetail.setVendorName(vendorDetail.getVendorLastName() + VendorConstants.NAME_DELIM + vendorDetail.getVendorFirstName());
308            }
309        }
310    
311        /**
312         * If the vendorFirstLastNameIndicator is true, this method will set the vendor first name and vendor last name fields from the
313         * vendorName field, then set the vendorName field to null. Then it sets the businessObject of this maintainable to the
314         * VendorDetail object that contains our modification to the name fields.
315         * 
316         * @see org.kuali.rice.kns.maintenance.Maintainable#saveBusinessObject()
317         */
318        @Override
319        public void setBusinessObject(PersistableBusinessObject bo) {
320            VendorDetail originalBo = (VendorDetail) bo;
321            String vendorName = originalBo.getVendorName();
322            if (originalBo.isVendorFirstLastNameIndicator() && ObjectUtils.isNotNull(vendorName)) {
323                int start = vendorName.indexOf(VendorConstants.NAME_DELIM);
324                if (start >= 0) {
325                    String lastName = vendorName.substring(0, start);
326                    String firstName = new String();
327                    if (start + VendorConstants.NAME_DELIM.length() <= vendorName.length()) {
328                        firstName = vendorName.substring(start + VendorConstants.NAME_DELIM.length(), vendorName.length());
329                    }
330    
331                    originalBo.setVendorFirstName((ObjectUtils.isNotNull(firstName) ? firstName.trim() : firstName));
332                    originalBo.setVendorLastName((ObjectUtils.isNotNull(lastName) ? lastName.trim() : lastName));
333                    originalBo.setVendorName(null);
334                }
335            }
336            this.businessObject = originalBo;
337        }
338    
339        /**
340         * 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
341         * 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
342         * for the count of vendor details in the database whose vendor header id match with the vendor header id of this new division,
343         * 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
344         * id and detail id as the count has existed. If a vendor with such criteria exists, this method will increment the count
345         * 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
346         * boolean flag to stop the loop, because we have already found the valid detail assigned id that we were looking for
347         * 
348         * @param vendorDetail VendorDetail The vendor whose detail assigned id we're trying to assign.
349         */
350        private void setDetailAssignedId(VendorDetail vendorDetail) {
351            // If this is a new parent, let's set the detail id to 0.
352            if (ObjectUtils.isNull(vendorDetail.getVendorHeaderGeneratedIdentifier())) {
353                vendorDetail.setVendorDetailAssignedIdentifier(new Integer(0));
354            }
355            else {
356                // Try to get the count of all the vendor whose header id is the same as this header id.
357                Map criterias = new HashMap();
358                criterias.put(VendorPropertyConstants.VENDOR_HEADER_GENERATED_ID, vendorDetail.getVendorHeaderGeneratedIdentifier());
359                BusinessObjectService boService = SpringContext.getBean(BusinessObjectService.class);
360                int count = boService.countMatching(VendorDetail.class, criterias);
361                boolean validId = false;
362                while (!validId) {
363                    criterias.put(VendorPropertyConstants.VENDOR_DETAIL_ASSIGNED_ID, count);
364                    int result = boService.countMatching(VendorDetail.class, criterias);
365                    if (result > 0) {
366                        // increment the detail id by 1
367                        count++;
368                    }
369                    else {
370                        // count is a validId, so we'll use count as our vendor detail assigned id
371                        validId = true;
372                        vendorDetail.setVendorDetailAssignedIdentifier(new Integer(count));
373                    }
374                }
375            }
376        }
377    
378        /**
379         * Returns the locking representation of the vendor. If the vendor detail id is not null, call the super class
380         * implementation of generateMaintenanceLocks which will set the locking key to be the header and detail ids. However, if the
381         * detail id is null, that means this is a new vendor (parent or division) and we should ignore locking.
382         * 
383         * @see org.kuali.rice.kns.maintenance.Maintainable#generateMaintenanceLocks()
384         */
385        @Override
386        public List<MaintenanceLock> generateMaintenanceLocks() {
387            if (ObjectUtils.isNotNull(((VendorDetail) getBusinessObject()).getVendorDetailAssignedIdentifier())) {
388                return super.generateMaintenanceLocks();
389            }
390            else {
391                return new ArrayList();
392            }
393        }
394    
395        /**
396         * Create a new division vendor if the user clicks on the "Create a new division" link. By default, the vendorParentIndicator is
397         * 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
398         * the vendorParentIndicator to false in this case.
399         * 
400         * @see org.kuali.rice.kns.maintenance.Maintainable#setupNewFromExisting()
401         */
402        @Override
403        public void setupNewFromExisting( MaintenanceDocument document, Map<String,String[]> parameters ) {
404            super.setupNewFromExisting(document, parameters);
405            ((VendorDetail) super.getBusinessObject()).setVendorParentIndicator(false);
406            ((VendorDetail) super.getBusinessObject()).setActiveIndicator(true);
407    
408            setVendorCreateAndUpdateNote(VendorConstants.VendorCreateAndUpdateNotePrefixes.ADD);
409        }
410    
411        /**
412         * @see org.kuali.rice.kns.maintenance.KualiMaintainableImpl#isRelationshipRefreshable(java.lang.Class, java.lang.String)
413         */
414        @Override
415        protected boolean isRelationshipRefreshable(Class boClass, String relationshipName) {
416            if (VendorDetail.class.isAssignableFrom(boClass) && VendorConstants.VENDOR_HEADER_ATTR.equals(relationshipName)) {
417                return false;
418            }
419            return super.isRelationshipRefreshable(boClass, relationshipName);
420        }
421    
422        /**
423         * @see org.kuali.kfs.sys.document.FinancialSystemMaintainable#answerSplitNodeQuestion(java.lang.String)
424         */
425        @Override
426        protected boolean answerSplitNodeQuestion(String nodeName) throws UnsupportedOperationException {
427            if (nodeName.equals("RequiresApproval")) return SpringContext.getBean(VendorService.class).shouldVendorRouteForApproval(this.documentNumber);
428            return super.answerSplitNodeQuestion(nodeName);
429        }
430    }
431