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