View Javadoc
1   /**
2    * Copyright 2004-2014 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.kpme.pm.position.web;
17  
18  import java.beans.IntrospectionException;
19  import java.beans.Introspector;
20  import java.beans.PropertyDescriptor;
21  import java.math.BigDecimal;
22  import java.util.*;
23  
24  import org.apache.commons.collections.CollectionUtils;
25  import org.apache.commons.lang.StringUtils;
26  import org.kuali.kpme.core.api.department.Department;
27  import org.kuali.kpme.core.bo.HrBusinessObject;
28  import org.kuali.kpme.core.bo.HrDataObjectMaintainableImpl;
29  import org.kuali.kpme.core.bo.derived.HrBusinessObjectDerived;
30  import org.kuali.kpme.core.departmentaffiliation.DepartmentAffiliationBo;
31  import org.kuali.kpme.core.service.HrServiceLocator;
32  import org.kuali.kpme.core.util.ValidationUtils;
33  import org.kuali.kpme.pm.position.PositionBo;
34  import org.kuali.kpme.pm.position.PositionDutyBo;
35  import org.kuali.kpme.pm.position.PositionQualificationBo;
36  import org.kuali.kpme.pm.position.PstnFlagBo;
37  import org.kuali.kpme.pm.position.funding.PositionFundingBo;
38  import org.kuali.kpme.pm.positiondepartment.PositionDepartmentBo;
39  import org.kuali.kpme.pm.positionresponsibility.PositionResponsibilityBo;
40  import org.kuali.kpme.pm.service.base.PmServiceLocator;
41  import org.kuali.rice.kew.api.document.DocumentStatus;
42  import org.kuali.rice.kew.api.exception.WorkflowException;
43  import org.kuali.rice.kim.api.identity.principal.EntityNamePrincipalName;
44  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
45  import org.kuali.rice.kns.service.KNSServiceLocator;
46  import org.kuali.rice.krad.bo.DocumentHeader;
47  import org.kuali.rice.krad.bo.Note;
48  import org.kuali.rice.krad.maintenance.MaintenanceDocument;
49  import org.kuali.rice.krad.service.KRADServiceLocator;
50  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
51  import org.kuali.rice.krad.uif.container.CollectionGroup;
52  import org.kuali.rice.krad.uif.view.View;
53  import org.kuali.rice.krad.uif.view.ViewModel;
54  import org.kuali.rice.krad.util.GlobalVariables;
55  import org.kuali.rice.krad.web.form.MaintenanceDocumentForm;
56  
57  public class PositionMaintainableServiceImpl extends HrDataObjectMaintainableImpl {
58  
59  	private static final long serialVersionUID = 1L;
60  
61  	@Override
62  	public HrBusinessObject getObjectById(String id) {
63  		return PositionBo.from(PmServiceLocator.getPositionService().getPosition(id));
64  	}
65  
66      @Override
67      public void customInactiveSaveLogicNewEffective(HrBusinessObject oldHrObj) {
68          PositionBo bo = (PositionBo)oldHrObj;
69          bo.setDepartmentList(null);
70          bo.setDutyList(null);
71          bo.setPositionResponsibilityList(null);
72          bo.setQualificationList(null);
73          bo.setFlagList(null);
74          bo.setFundingList(null);
75          bo.setRequiredQualList(null);
76      }
77  
78      @Override
79  	public void customSaveLogic(HrBusinessObject hrObj){
80  		PositionBo aPosition = (PositionBo) hrObj;
81  		for(PositionQualificationBo aQual : aPosition.getQualificationList()) {
82  			aQual.setHrPositionId(aPosition.getHrPositionId());
83  			aQual.setPmQualificationId(null);
84  		}
85  		for(PositionDutyBo aDuty : aPosition.getDutyList()) {
86  			aDuty.setHrPositionId(aPosition.getHrPositionId());
87  			aDuty.setPmDutyId(null);
88  		}
89  		for(PstnFlagBo aFlag : aPosition.getFlagList()) {
90  			aFlag.setHrPositionId(aPosition.getHrPositionId());
91  			aFlag.setPmFlagId(null);
92  		}
93  		for(PositionFundingBo aFunding : aPosition.getFundingList()) {
94  			aFunding.setHrPositionId(aPosition.getHrPositionId());
95  			aFunding.setPmPositionFunctionId(null);
96  		}
97          for(PositionDepartmentBo aDepartment : aPosition.getDepartmentList()) {
98              aDepartment.setHrPositionId(aPosition.getHrPositionId());
99              aDepartment.setPmPositionDeptId(null);
100         }
101         for(PositionResponsibilityBo aResponsibility : aPosition.getPositionResponsibilityList()) {
102         	aResponsibility.setHrPositionId(aPosition.getHrPositionId());
103         	aResponsibility.setPositionResponsibilityId(null);
104         }
105         
106         // KPME-3016 populate institution and location here
107         // We should be able to do this in addNewLineToCollection, but all the components are in "pages" now with the layout change, 
108         // not on the form, and addNewLineToCollection doesn't get called. 
109         if (aPosition.getDepartmentList() != null) {
110         	for(PositionDepartmentBo aPositionDepartment : aPosition.getDepartmentList()) {
111         		if(aPositionDepartment != null && aPositionDepartment.getDeptAffl() != null) {
112         			DepartmentAffiliationBo pda = (DepartmentAffiliationBo)aPositionDepartment.getDeptAfflObj();
113         			if (pda.isPrimaryIndicator()) {
114         				aPosition.setGroupKeyCode(aPositionDepartment.getGroupKeyCode());
115         				break;
116         			}
117         		}
118         	}
119         }
120 
121 	}
122 	
123 	@Override
124     protected boolean performAddLineValidation(ViewModel viewModel, Object newLine, String collectionId,
125                                                String collectionPath) {
126         boolean isValid = super.performAddLineValidation(viewModel, newLine, collectionId, collectionPath);
127         if (viewModel instanceof MaintenanceDocumentForm) {
128 	        MaintenanceDocumentForm maintenanceForm = (MaintenanceDocumentForm) viewModel;
129 	        MaintenanceDocument document = maintenanceForm.getDocument();
130 	        if (document.getNewMaintainableObject().getDataObject() instanceof PositionBo) {
131 	        	PositionBo aPosition = (PositionBo) document.getNewMaintainableObject().getDataObject();
132 	        	// Duty line validation
133 		        if (newLine instanceof PositionDutyBo) {
134 		        	PositionDutyBo pd = (PositionDutyBo) newLine;
135 		        	boolean results = this.validateDutyListPercentage(pd, aPosition);
136 		        	if(!results) {
137 		        		return false;
138 		        	}
139 		        }
140 	        	// Funding line validation
141 		        if (newLine instanceof PositionFundingBo) {
142 		        	PositionFundingBo pf = (PositionFundingBo) newLine;
143 		        	boolean results = this.validateAddFundingLine(pf, aPosition);
144 		        	if(!results) {
145 		        		return false;
146 		        	}
147 		        }
148 		        
149 		        // Responsibility validation
150 		        if(newLine instanceof PositionResponsibilityBo) {
151 		        	PositionResponsibilityBo pr = (PositionResponsibilityBo) newLine;
152 		        	boolean results = this.validatePositionResponsibilityListPercentage(pr, aPosition);
153 		        	if(!results) {
154 		        		return false;
155 		        	}
156 		        }
157 		        
158 		        //Department validation
159 		        if(newLine instanceof PositionDepartmentBo) {
160 		        	PositionDepartmentBo pd = (PositionDepartmentBo) newLine;
161 		        	boolean results = this.validateAdditionalDepartmentList(pd,aPosition);
162 		        	if(!results){
163 		        		return false;
164 		        	}
165 		        }
166 	        }
167         }
168 
169         return isValid;
170     }
171 	
172 	private boolean validateAdditionalDepartmentList(PositionDepartmentBo pd,
173 			PositionBo aPosition) {
174 		
175 		//Will only be validated if effective local date in position is not null
176 		if(aPosition.getEffectiveLocalDate()!=null && pd != null){
177 			Department department = HrServiceLocator.getDepartmentService().getDepartment(pd.getDepartment(), pd.getGroupKeyCode(), aPosition.getEffectiveLocalDate());
178 			if(department == null){
179 				GlobalVariables.getMessageMap().putError("newCollectionLines['document.newMaintainableObject.dataObject.departmentList'].department", "error.existence", "Position Department '" + pd.getDepartment() + "'");
180 				return false;
181 			}
182 		}
183 
184 		return true;
185 	}
186 
187 	private boolean validateDutyListPercentage(PositionDutyBo pd, PositionBo aPosition) {
188 		if(CollectionUtils.isNotEmpty(aPosition.getDutyList()) && pd.getPercentage() != null) {
189 			BigDecimal sum = pd.getPercentage();
190 			for(PositionDutyBo aDuty : aPosition.getDutyList()) {
191 				if(aDuty != null && aDuty.getPercentage() != null) {
192 					sum = sum.add(aDuty.getPercentage());
193 				}
194 			}
195 			if(sum.compareTo(new BigDecimal(100)) > 0) {
196 				GlobalVariables.getMessageMap().putError("newCollectionLines['document.newMaintainableObject.dataObject.dutyList'].percentage", "duty.percentage.exceedsMaximum", sum.toString());
197 				return false;
198 			}
199 		}		
200 		return true;
201 	}
202 	
203 	private boolean validatePositionResponsibilityListPercentage(PositionResponsibilityBo pd, PositionBo aPosition) {
204 		if(CollectionUtils.isNotEmpty(aPosition.getPositionResponsibilityList()) && pd.getPercentTime() != null) {
205 			BigDecimal sum = pd.getPercentTime();
206 			for(PositionResponsibilityBo aResponsibility : aPosition.getPositionResponsibilityList()) {
207 				if(aResponsibility != null && aResponsibility.getPercentTime() != null) {
208 					sum = sum.add(aResponsibility.getPercentTime());
209 				}
210 			}
211 			if(sum.compareTo(new BigDecimal(100)) > 0) {
212 				GlobalVariables.getMessageMap().putError("newCollectionLines['document.newMaintainableObject.dataObject.positionResponsibilityList'].percentTime", "responsibility.percenttime.exceedsMaximum", sum.toString());
213 				return false;
214 			}
215 		}		
216 		return true;
217 	}
218 
219 	protected boolean validateAddFundingLine(PositionFundingBo pf, PositionBo aPosition) {
220     	if(StringUtils.isNotEmpty(pf.getAccount())) {
221     		boolean results = ValidationUtils.validateAccount(pf.getChart(), pf.getAccount());
222     		if(!results) {
223     			GlobalVariables.getMessageMap().putError("newCollectionLines['document.newMaintainableObject.dataObject.fundingList'].account","error.existence", "Account '" + pf.getAccount() + "'");
224     			return results;
225     		}
226     	}
227     	if(StringUtils.isNotEmpty(pf.getSubAccount())) {
228     		boolean results = ValidationUtils.validateSubAccount(pf.getSubAccount(), pf.getAccount(), pf.getChart());
229     		if(!results) {
230 	   			 GlobalVariables.getMessageMap().putError("newCollectionLines['document.newMaintainableObject.dataObject.fundingList'].subAccount","error.existence", "Sub Account '" + pf.getSubAccount() + "'");
231 	   			 return results;
232     		}
233     	}
234     	if(StringUtils.isNotEmpty(pf.getObjectCode())) {
235     		boolean results = ValidationUtils.validateObjectCode(pf.getObjectCode(), pf.getChart(), null);
236     		if(!results) {
237     			 GlobalVariables.getMessageMap().putError("newCollectionLines['document.newMaintainableObject.dataObject.fundingList'].objectCode","error.existence", "Object Code '" + pf.getObjectCode() + "'");
238       			 return results;
239     		}
240     	}
241     	if(StringUtils.isNotEmpty(pf.getSubObjectCode())) {
242     		boolean results = ValidationUtils.validateSubObjectCode(null,
243     				pf.getChart(),
244     				pf.getAccount(),
245     				pf.getObjectCode(),
246     				pf.getSubObjectCode());
247     		if(!results) {
248     			 GlobalVariables.getMessageMap().putError("newCollectionLines['document.newMaintainableObject.dataObject.fundingList'].subObjectCode","error.existence", "Sub Object Code '" + pf.getSubObjectCode() + "'");
249       			 return results;
250     		}
251     	}
252     	return true;
253     
254 	}
255 
256 	
257 	// KPME-3016
258 	//set document description here so it passes validation.  It will get overriden in doRouteStatusChange method
259 	@Override
260 	public void processAfterEdit(MaintenanceDocument document, Map<String, String[]> requestParameters) {
261         document.getDocumentHeader().setDocumentDescription("Edit Position");
262         super.processAfterEdit(document, requestParameters);
263     }
264 	
265 	
266 	protected void setupNewPositionRecord(MaintenanceDocument document) {
267 		PositionBo aPosition = (PositionBo) document.getNewMaintainableObject().getDataObject();
268         aPosition.setProcess("New");
269         String positionNumber = KNSServiceLocator.getSequenceAccessorService().getNextAvailableSequenceNumber("hr_position_s", PositionBo.class).toString();
270         aPosition.setPositionNumber(positionNumber);
271         
272         document.getDocumentHeader().setDocumentDescription("New Position");
273 	}
274 	
275 	
276 	@Override 
277 	public void processAfterNew(MaintenanceDocument document, Map<String, String[]> requestParameters) {
278 		setupNewPositionRecord(document);
279 		super.processAfterNew(document, requestParameters);
280 	}
281 	
282 	@Override
283 	public void processAfterCopy(MaintenanceDocument document, Map<String, String[]> parameters) {
284 		setupNewPositionRecord(document);
285 		super.processAfterCopy(document, parameters);
286 	}
287 
288     @Override
289     public String getDocumentTitle(MaintenanceDocument document) {
290         String docTitle = document.getDocumentHeader().getDocumentDescription();
291         return docTitle;
292     }
293 
294 	@Override
295     public void doRouteStatusChange(DocumentHeader documentHeader) {
296 
297 		String docDescription = null;
298 		PositionBo position = (PositionBo)this.getDataObject();
299 		DocumentStatus documentStatus = documentHeader.getWorkflowDocument().getStatus();
300 	
301 		//Set document description for real here
302 		if (StringUtils.isEmpty(position.getPositionNumber())) {
303 			docDescription = "Process: " + position.getProcess() + " Position Status: " + position.getPositionStatus();
304 		} else {
305 			docDescription = "Process: " + position.getProcess() + " Position Number: " + position.getPositionNumber() + " Position Status: " + position.getPositionStatus();
306 		}
307 
308 		if (DocumentStatus.ENROUTE.equals(documentStatus)) {
309 			try {
310 				MaintenanceDocument md = (MaintenanceDocument)KRADServiceLocatorWeb.getDocumentService().getByDocumentHeaderId(documentHeader.getDocumentNumber());
311 		        md.getDocumentHeader().setDocumentDescription(docDescription);
312 		        md.getNewMaintainableObject().setDataObject(position);
313 		        KRADServiceLocatorWeb.getDocumentService().saveDocument(md);
314 			} catch (WorkflowException e) {
315 	            LOG.error("caught exception while handling doRouteStatusChange -> documentService.getByDocumentHeaderId(" + documentHeader.getDocumentNumber() + "). ", e);
316 	            throw new RuntimeException("caught exception while handling doRouteStatusChange -> documentService.getByDocumentHeaderId(" + documentHeader.getDocumentNumber() + "). ", e);
317 	        }
318 		}
319     }
320 
321     //KPME-2624 added logic to save current logged in user to UserPrincipal id for collections
322     @Override
323     public void prepareForSave() {
324     	PositionBo position = (PositionBo)this.getDataObject();
325         boolean hasPrimaryDepartment = false;
326         for (PositionDepartmentBo positionDepartment : position.getDepartmentList()) {
327             if (positionDepartment.getDeptAfflObj().isPrimaryIndicator()) {
328                 hasPrimaryDepartment=true;
329                 positionDepartment.setDepartment(position.getPrimaryDepartment());
330                 positionDepartment.setGroupKeyCode(position.getGroupKeyCode());
331 //                positionDepartment.setLocation(position.getLocation());
332 //                positionDepartment.setInstitution(position.getInstitution());
333                 positionDepartment.setDeptAffl(HrServiceLocator.getDepartmentAffiliationService().getPrimaryAffiliation().getDeptAfflType());
334             }
335         }
336 
337         //create primary department
338         if (!hasPrimaryDepartment && StringUtils.isNotEmpty(position.getPrimaryDepartment())) {
339             PositionDepartmentBo primaryDepartment = new PositionDepartmentBo();
340             primaryDepartment.setDepartment(position.getPrimaryDepartment());
341             primaryDepartment.setGroupKeyCode(position.getGroupKeyCode());
342             // primaryDepartment.setLocation(position.getLocation());
343             // primaryDepartment.setInstitution(position.getInstitution());
344             primaryDepartment.setDeptAffl(HrServiceLocator.getDepartmentAffiliationService().getPrimaryAffiliation().getDeptAfflType());
345             position.getDepartmentList().add(primaryDepartment);
346         }
347 
348         //add note if enroute change occurs
349             try {
350                 MaintenanceDocument maintenanceDocument = (MaintenanceDocument) KRADServiceLocatorWeb.getDocumentService().getByDocumentHeaderId(this.getDocumentNumber());
351                 if (maintenanceDocument != null && maintenanceDocument.getNewMaintainableObject().getDataObject() instanceof PositionBo) {
352                     PositionBo previousPosition = (PositionBo) maintenanceDocument.getNewMaintainableObject().getDataObject();
353                     recordEnrouteChanges(previousPosition,maintenanceDocument.getNoteTarget().getObjectId());
354                 }
355             } catch (Exception e) {
356                 e.printStackTrace();
357             }
358 
359         super.prepareForSave();
360 
361 
362     }
363 
364     private void recordEnrouteChanges(PositionBo previousPosition, String noteTarget) {
365         //List of fields on the position class not to compare
366         List<String> noCompareFields = new ArrayList<String>();
367         noCompareFields.add("process");
368         noCompareFields.add("requiredQualList");
369 
370         List<Note> noteList = new ArrayList<Note>();
371         PositionBo currentPosition = (PositionBo) this.getDataObject();
372 
373         EntityNamePrincipalName approver = KimApiServiceLocator.getIdentityService().getDefaultNamesForPrincipalId(currentPosition.getUserPrincipalId());
374 
375         //compare all fields on position
376         try {
377             for (PropertyDescriptor pd : Introspector.getBeanInfo(PositionBo.class).getPropertyDescriptors()) {
378 
379                 if (pd.getReadMethod() != null && !noCompareFields.contains(pd.getName())) {
380                     try {
381                         Object currentObject = pd.getReadMethod().invoke(currentPosition);
382                         Object previousObject = pd.getReadMethod().invoke(previousPosition);
383                             if (currentObject instanceof Collection) {
384                                 if (!compareCollections(currentObject,previousObject)) {
385                                     String noteText = approver.getPrincipalName() + " changed " + pd.getDisplayName() + " from '" + previousObject.toString() + "' to '" + currentObject.toString() + "'";
386                                     noteList.add(createNote(noteText,noteTarget,currentPosition.getUserPrincipalId()));
387                                 }
388                             } else {
389                                 if (!(currentObject == null ? previousObject == null : currentObject.equals(previousObject))){
390                                         String noteText = approver.getPrincipalName() + " changed " + pd.getDisplayName() + " from '" + previousObject.toString() + "' to '" + currentObject.toString() + "'";
391                                         noteList.add(createNote(noteText,noteTarget,currentPosition.getUserPrincipalId()));
392                                 }
393                             }
394 
395                     } catch (Exception e) {
396                         e.printStackTrace();
397                     }
398                 }
399             }
400         } catch (IntrospectionException e) {
401             e.printStackTrace();
402         }
403 
404         KRADServiceLocator.getNoteService().saveNoteList(noteList);
405     }
406 
407     @SuppressWarnings("rawtypes")
408 	public boolean compareCollections(Object coll1, Object coll2) {
409         if (coll1 == coll2)
410             return true;
411 
412         if (coll1 instanceof List && coll2 instanceof List) {
413             ListIterator list1 = ((List) coll1).listIterator();
414             ListIterator list2 = ((List) coll2).listIterator();
415             while (list1.hasNext() && list2.hasNext()) {
416                 Object o1 = list1.next();
417                 Object o2 = list2.next();
418 
419                 if (o1 instanceof HrBusinessObjectDerived && o1 instanceof HrBusinessObjectDerived) {
420                     HrBusinessObjectDerived hrObj1 = (HrBusinessObjectDerived) o1;
421                     HrBusinessObjectDerived hrObj2 = (HrBusinessObjectDerived) o2;
422                     if (!(hrObj1 == null ? hrObj2 == null : hrObj1.isEquivalentTo(hrObj2)))
423                         return false;
424                 } else {
425                     if (!(o1 == null ? o2 == null : o1.equals(o2)))
426                         return false;
427                 }
428             }
429 
430             return !(list1.hasNext() || list2.hasNext());
431         } else {
432             //need to add logic if other collection types are added to position
433             return coll1.equals(coll2);
434         }
435     }
436 
437     private Note createNote(String noteText, String noteTarget, String principalId) {
438         Note note = new Note();
439         note.setRemoteObjectIdentifier(noteTarget);
440         note.setNoteText(StringUtils.abbreviate(noteText,800));
441         note.setAuthorUniversalIdentifier(principalId);
442         note.setNotePostedTimestampToCurrent();
443         return note;
444     }
445 
446 }