View Javadoc

1   /**
2    * Copyright 2012 The Kuali Foundation Licensed under the
3    * Educational Community License, Version 2.0 (the "License"); you may
4    * not use this file except in compliance with the License. You may
5    * obtain a copy of the License at
6    *
7    * http://www.osedu.org/licenses/ECL-2.0
8    *
9    * Unless required by applicable law or agreed to in writing,
10   * software distributed under the License is distributed on an "AS IS"
11   * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12   * or implied. See the License for the specific language governing
13   * permissions and limitations under the License.
14   *
15   */
16  package org.kuali.student.enrollment.class2.courseoffering.util;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.kuali.rice.kim.api.identity.Person;
20  import org.kuali.rice.kim.api.identity.PersonService;
21  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
22  import org.kuali.rice.kim.impl.KIMPropertyConstants;
23  import org.kuali.student.enrollment.class2.courseoffering.dto.CourseOfferingListSectionWrapper;
24  import org.kuali.student.enrollment.courseoffering.dto.ActivityOfferingInfo;
25  import org.kuali.student.enrollment.courseoffering.dto.CourseOfferingCrossListingInfo;
26  import org.kuali.student.enrollment.courseoffering.dto.CourseOfferingInfo;
27  import org.kuali.student.enrollment.courseoffering.dto.FormatOfferingInfo;
28  import org.kuali.student.enrollment.courseoffering.dto.OfferingInstructorInfo;
29  import org.kuali.student.enrollment.courseoffering.service.CourseOfferingService;
30  import org.kuali.student.r2.common.dto.ContextInfo;
31  import org.kuali.student.r2.common.dto.StatusInfo;
32  import org.kuali.student.r2.common.exceptions.DataValidationErrorException;
33  import org.kuali.student.r2.common.exceptions.DoesNotExistException;
34  import org.kuali.student.r2.common.exceptions.InvalidParameterException;
35  import org.kuali.student.r2.common.exceptions.MissingParameterException;
36  import org.kuali.student.r2.common.exceptions.OperationFailedException;
37  import org.kuali.student.r2.common.exceptions.PermissionDeniedException;
38  import org.kuali.student.r2.common.exceptions.ReadOnlyException;
39  import org.kuali.student.r2.common.exceptions.VersionMismatchException;
40  import org.kuali.student.r2.common.util.ContextUtils;
41  import org.kuali.student.r2.common.util.constants.LuiServiceConstants;
42  import org.kuali.student.r2.core.acal.dto.KeyDateInfo;
43  import org.kuali.student.r2.core.acal.dto.TermInfo;
44  import org.kuali.student.r2.core.acal.service.AcademicCalendarService;
45  import org.kuali.student.r2.core.class1.search.CourseOfferingManagementSearchImpl;
46  import org.kuali.student.r2.core.class1.type.dto.TypeTypeRelationInfo;
47  import org.kuali.student.r2.core.class1.type.service.TypeService;
48  import org.kuali.student.r2.core.constants.AtpServiceConstants;
49  import org.kuali.student.r2.core.constants.TypeServiceConstants;
50  import org.kuali.student.r2.core.scheduling.dto.ScheduleRequestSetInfo;
51  import org.kuali.student.r2.core.scheduling.service.SchedulingService;
52  import org.kuali.student.r2.core.search.dto.SearchParamInfo;
53  import org.kuali.student.r2.core.search.dto.SearchRequestInfo;
54  import org.kuali.student.r2.core.search.dto.SearchResultCellInfo;
55  import org.kuali.student.r2.core.search.dto.SearchResultInfo;
56  import org.kuali.student.r2.core.search.dto.SearchResultRowInfo;
57  import org.kuali.student.r2.core.search.service.SearchService;
58  import org.kuali.student.r2.lum.clu.service.CluService;
59  import org.kuali.student.r2.lum.course.dto.ActivityInfo;
60  import org.kuali.student.r2.lum.course.dto.CourseInfo;
61  import org.kuali.student.r2.lum.course.dto.FormatInfo;
62  import org.kuali.student.r2.lum.course.service.CourseService;
63  import org.kuali.student.r2.lum.lrc.service.LRCService;
64  
65  import java.util.ArrayList;
66  import java.util.Arrays;
67  import java.util.Calendar;
68  import java.util.Collection;
69  import java.util.Collections;
70  import java.util.Date;
71  import java.util.HashMap;
72  import java.util.List;
73  import java.util.Map;
74  
75  /**
76   * This class //TODO ... This calss is specific to CourseOffering and should be renamed appropriately
77   *
78   * @author Kuali Student Team
79   */
80  public class CourseOfferingViewHelperUtil {
81  
82      private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(CourseOfferingViewHelperUtil.class);
83  
84      public static List<Person> getInstructorByPersonId(String personId){
85          Map<String, String> searchCriteria = new HashMap<String, String>();
86          searchCriteria.put(KIMPropertyConstants.Person.PRINCIPAL_ID, personId);
87          List<Person> lstPerson = getPersonService().findPeople(searchCriteria);
88          return lstPerson;
89      }
90  
91      public static PersonService getPersonService() {
92          return KimApiServiceLocator.getPersonService();
93      }
94  
95      public static String trimTrailing0(String creditValue){
96          if (creditValue.indexOf(".0") > 0) {
97              return creditValue.substring(0, creditValue.length( )- 2);
98          } else {
99              return creditValue;
100         }
101     }
102 
103     public static LRCService getLrcService() {
104         return CourseOfferingResourceLoader.loadLrcService();
105     }
106 
107     public static CourseService getCourseService() {
108         return CourseOfferingResourceLoader.loadCourseService();
109     }
110 
111     public static OfferingInstructorInfo findDisplayInstructor(List<OfferingInstructorInfo> instructors) {
112         OfferingInstructorInfo result = null;
113 
114         if(instructors != null && !instructors.isEmpty()) {
115 
116             // Build the display name for the Instructor
117             Collection<OfferingInstructorInfo> highestInstEffortInstructors = new ArrayList<OfferingInstructorInfo>();
118             float highestInstEffortComparison = 0f;
119 
120             for (OfferingInstructorInfo instructor : instructors) {
121                 if(instructor.getPercentageEffort() != null){
122                     // if this instructor has a higher percent effort than any previous instructors,
123                     // clear the list we are keeping track of and set the new comparison number to this instructor's percentage effort
124                     if(instructor.getPercentageEffort() > highestInstEffortComparison) {
125                         highestInstEffortInstructors.clear();
126                         highestInstEffortComparison = instructor.getPercentageEffort();
127                         highestInstEffortInstructors.add(instructor);
128                     }
129                     // if this instructor's percent effort is tied with the comparison number,
130                     // add this instructor to the list of highest effort instructors
131                     else if (instructor.getPercentageEffort() == highestInstEffortComparison) {
132                         highestInstEffortInstructors.add(instructor);
133                     }
134                 }
135             }
136 
137             if(highestInstEffortInstructors.size() == 1) {
138                 result = highestInstEffortInstructors.iterator().next();
139             }
140             else {
141                 List<String> names = new ArrayList<String>(highestInstEffortInstructors.size());
142                 Map<String, OfferingInstructorInfo> nameMap = new HashMap<String, OfferingInstructorInfo>(highestInstEffortInstructors.size());
143                 for(OfferingInstructorInfo oiInfo : highestInstEffortInstructors) {
144                     names.add(oiInfo.getPersonName());
145                     nameMap.put(oiInfo.getPersonName(), oiInfo);
146                 }
147 
148                 Collections.sort(names);
149 
150                 result = nameMap.get(names.get(0));
151             }
152         }
153 
154         return result;
155     }
156 
157     public static   CourseOfferingListSectionWrapper convertCourseOffering2ListSectionWrapper(CourseOfferingInfo coInfo){
158         CourseOfferingListSectionWrapper coWrapper = new CourseOfferingListSectionWrapper();
159         coWrapper.setSubjectArea(coInfo.getSubjectArea());
160         coWrapper.setCourseOfferingCode(coInfo.getCourseOfferingCode());
161         coWrapper.setCourseOfferingCreditOptionKey(coInfo.getCreditOptionId());
162         coWrapper.setCourseOfferingGradingOptionKey(coInfo.getGradingOptionId());
163         coWrapper.setCourseOfferingStateKey(coInfo.getStateKey());
164         coWrapper.setCourseOfferingDesc(coInfo.getCourseOfferingTitle());
165         coWrapper.setCourseOfferingId(coInfo.getId());
166         return coWrapper;
167     }
168 
169     public static void updateCourseOfferingStateFromActivityOfferingStateChange(CourseOfferingInfo coInfo, ContextInfo context) throws InvalidParameterException, MissingParameterException, DoesNotExistException, PermissionDeniedException, OperationFailedException, DataValidationErrorException, VersionMismatchException, ReadOnlyException {
170        updateCourseOfferingStateFromActivityOfferingStateChange(convertCourseOffering2ListSectionWrapper(coInfo),context);
171     }
172 
173     /**
174      * Evaluates whether to update the state of a Course Offering (and possibly its Format Offerings) based on
175      * the state of its Activity Offerings
176      *
177      * This is a utility method that combines logic for updating related objects when the state of one or more
178      * Activity Offerings is changed.
179      *
180      */
181     public static void updateCourseOfferingStateFromActivityOfferingStateChange(CourseOfferingListSectionWrapper coInfo, ContextInfo context) throws InvalidParameterException, MissingParameterException, DoesNotExistException, PermissionDeniedException, OperationFailedException, DataValidationErrorException, VersionMismatchException, ReadOnlyException {
182 
183         CourseOfferingService coService = CourseOfferingResourceLoader.loadCourseOfferingService();
184 
185         List<FormatOfferingInfo> formatOfferings = coService.getFormatOfferingsByCourseOffering(coInfo.getCourseOfferingId(), context);
186 
187         String oldFoState, newFoState;
188         // Verify each FO, CO state with AO state consistence
189 
190         for (FormatOfferingInfo fo : formatOfferings) {
191             oldFoState = fo.getStateKey();
192             List<ActivityOfferingInfo> activityOfferings = coService.getActivityOfferingsByFormatOffering(fo.getId(), context);
193             newFoState = getNewFoState(activityOfferings);
194 
195             if (newFoState != null && !StringUtils.equals(oldFoState, newFoState)) {
196                 fo.setStateKey(newFoState);
197                 StatusInfo statusInfo = coService.changeFormatOfferingState(fo.getId(), newFoState, context);
198                 if (!statusInfo.getIsSuccess()){
199                      throw new RuntimeException(statusInfo.getMessage());
200                 }
201             }
202         }
203     }
204 
205     // if all of the AO states are Draft or Approved, the FO that owns the AO's cannot be Offered.  
206     // If no FO is Offered, then the CO which owns the FO's cannot be Offered.  
207     // It is possible for a CO with multiple FO's to have an Offered FO and one or more FO's in a non-Offered state;
208     // in that case, since at least one of the FO's is Offered, the CO which owns it is Offered.
209 
210     // If all of the AO's owned by an FO are deleted, that FO's state will change to Draft.  
211     // As above, if all of the FO's owned by a CO are Draft, the CO's state reverts to Draft;
212     // however, if the CO owns multiple FO's and only one of the FO's reverts to Draft, the CO state doesn't change if any of the other FO's is not in Draft state.
213     public static String getNewFoState(List<ActivityOfferingInfo> activityOfferings) {
214         boolean draftState= false;
215         boolean plannedState= false;
216         boolean offeredState= false;
217         boolean cancelledState= false;
218 
219         if(activityOfferings == null || activityOfferings.size() == 0) {
220             return LuiServiceConstants.LUI_FO_STATE_DRAFT_KEY;
221         }
222 
223         for (ActivityOfferingInfo ao : activityOfferings) {
224             if (StringUtils.equals(LuiServiceConstants.LUI_AO_STATE_APPROVED_KEY, ao.getStateKey()) ||
225                     StringUtils.equals(LuiServiceConstants.LUI_AO_STATE_SUBMITTED_KEY, ao.getStateKey()) ) {
226                 plannedState = true;
227             }  else if (StringUtils.equals(LuiServiceConstants.LUI_AO_STATE_OFFERED_KEY, ao.getStateKey())) {
228                 offeredState = true;
229             }  else if (StringUtils.equals(LuiServiceConstants.LUI_AO_STATE_CANCELED_KEY, ao.getStateKey())) {
230                 cancelledState = true;
231             }  else if (StringUtils.equals(LuiServiceConstants.LUI_AO_STATE_DRAFT_KEY, ao.getStateKey())) {
232                 draftState = true;
233             }
234         }
235 
236         // if ALL the AOs within this FO are in a draft state (or if there are no AOs), and the current state of the FO is Planned, update the FO state to Draft
237         if (offeredState) {
238             return LuiServiceConstants.LUI_FO_STATE_OFFERED_KEY;
239         }  else if(plannedState) {
240             return LuiServiceConstants.LUI_FO_STATE_PLANNED_KEY;
241         }  else if (draftState) {
242             return LuiServiceConstants.LUI_FO_STATE_DRAFT_KEY;
243         }
244 
245         // no offered, planned, and draft state
246         if(cancelledState)  {
247             return LuiServiceConstants.LUI_FO_STATE_CANCELED_KEY;
248         }
249         // If all AOs are suspended
250         return null;
251     }
252 
253     public static String getNewCoState(List<FormatOfferingInfo>  formatOfferings) {
254         boolean draftState= false;
255         boolean plannedState= false;
256         boolean offeredState= false;
257         boolean cancelledState= false;
258 
259         if(formatOfferings == null || formatOfferings.size() == 0) {
260             return LuiServiceConstants.LUI_CO_STATE_DRAFT_KEY;
261         }
262 
263         for (FormatOfferingInfo fo : formatOfferings) {
264              if(StringUtils.equals(LuiServiceConstants.LUI_FO_STATE_DRAFT_KEY, fo.getStateKey())) {
265                  draftState = true;
266              } else if(StringUtils.equals(LuiServiceConstants.LUI_FO_STATE_PLANNED_KEY, fo.getStateKey())) {
267                  plannedState = true;
268              } else if(StringUtils.equals(LuiServiceConstants.LUI_FO_STATE_OFFERED_KEY, fo.getStateKey())) {
269                  offeredState = true;
270              } else if(StringUtils.equals(LuiServiceConstants.LUI_FO_STATE_CANCELED_KEY, fo.getStateKey())) {
271                  cancelledState = true;
272              }
273         }
274 
275         if (offeredState) {
276             return LuiServiceConstants.LUI_CO_STATE_OFFERED_KEY;
277         }  else if(plannedState) {
278             return LuiServiceConstants.LUI_CO_STATE_PLANNED_KEY;
279         }  else if (draftState) {
280             return LuiServiceConstants.LUI_CO_STATE_DRAFT_KEY;
281         }
282 
283         // no offered, planned, and draft state
284         if(cancelledState)  {
285             return LuiServiceConstants.LUI_CO_STATE_CANCELED_KEY;
286         }
287         // Something wrong return null
288         return null;
289     }
290 
291     public static String createTheCrossListedCos(CourseOfferingInfo coToShow){
292         if (coToShow != null && coToShow.getCrossListings() != null && coToShow.getCrossListings().size() > 0) {
293             // Always include an option for Course
294             StringBuffer crossListedCodes = new StringBuffer();
295 
296             for (CourseOfferingCrossListingInfo courseInfo : coToShow.getCrossListings()) {
297                 crossListedCodes.append(courseInfo.getCode());
298                 crossListedCodes.append(" ");
299             }
300             return crossListedCodes.toString();
301         }
302         return null;
303     }
304 
305     /**
306      * Builds a string of course and activity codes for display.
307      * @param ao
308      * @param context
309      */
310     public static String createColocatedDisplayData(ActivityOfferingInfo ao, ContextInfo context) throws InvalidParameterException, MissingParameterException, PermissionDeniedException,
311             OperationFailedException, DoesNotExistException {
312         StringBuffer buffer = new StringBuffer();
313         buffer.append(" ");
314         CourseOfferingService coService = CourseOfferingResourceLoader.loadCourseOfferingService();
315         SchedulingService schedulingService = CourseOfferingResourceLoader.loadSchedulingService();
316         List<ScheduleRequestSetInfo> scheduleRequestSets = schedulingService.getScheduleRequestSetsByRefObject(ao.getTypeKey(), ao.getId(), context);
317         for(ScheduleRequestSetInfo srs : scheduleRequestSets) {
318             List<ActivityOfferingInfo> aoList = coService.getActivityOfferingsByIds(srs.getRefObjectIds(), context);
319             for(ActivityOfferingInfo aoInfo : aoList) {
320                 buffer.append(aoInfo.getCourseOfferingCode() + " " + aoInfo.getActivityCode() + " ");
321             }
322         }
323         return buffer.toString();
324     }
325 
326     /**
327      * This method loads the wrapper details for the joint courses
328      *
329      * @param courseName CourseOfferingCreateWrapper
330      * @throws Exception throws one of the services exceptions
331      */
332     public static List<CourseInfo> getMatchingCoursesFromClu(String courseName) {
333 
334         CourseInfo returnCourseInfo;
335         String courseId;
336         List<SearchParamInfo> searchParams = new ArrayList<SearchParamInfo>();
337         List<CourseInfo> courseInfoList = new ArrayList<CourseInfo>();
338         CluService cluService = CourseOfferingResourceLoader.loadCluService();
339 
340         SearchParamInfo qpv1 = new SearchParamInfo();
341         qpv1.setKey("lu.queryParam.startsWith.cluCode");
342         qpv1.getValues().add(courseName.toUpperCase());
343         searchParams.add(qpv1);
344 
345         SearchParamInfo qpv2 = new SearchParamInfo();
346         qpv2.setKey("lu.queryParam.cluState");
347         qpv2.setValues(Arrays.asList("Active"));
348         searchParams.add(qpv2);
349 
350         SearchRequestInfo searchRequest = new SearchRequestInfo();
351         searchRequest.setParams(searchParams);
352         searchRequest.setSearchKey("lu.search.cluByCodeAndState");
353 
354         try {
355             SearchResultInfo searchResult = cluService.search(searchRequest, ContextUtils.getContextInfo());
356             if (searchResult.getRows().size() > 0) {
357                 for (SearchResultRowInfo row : searchResult.getRows()) {
358                     List<SearchResultCellInfo> srCells = row.getCells();
359                     if (srCells != null && srCells.size() > 0) {
360                         for (SearchResultCellInfo cell : srCells) {
361                             if ("lu.resultColumn.cluId".equals(cell.getKey())) {
362                                 courseId = cell.getValue();
363                                 returnCourseInfo = getCourseService().getCourse(courseId, ContextUtils.getContextInfo());
364                                 courseInfoList.add(returnCourseInfo);
365                             }
366                         }
367                     }
368                 }
369             }
370         } catch (Exception e) {
371             throw new RuntimeException(e);
372         }
373 
374         return courseInfoList;
375     }
376 
377     /**
378      * Adds correct AO types to the format offering
379      * (Code fix for KSENROLL-6071, KSNEROLL-6074)
380      * @param fo The FO to fix up
381      * @param course Which course this is pertinent to
382      * @param context
383      * @throws PermissionDeniedException
384      * @throws MissingParameterException
385      * @throws InvalidParameterException
386      * @throws OperationFailedException
387      * @throws DoesNotExistException
388      */
389     public static void addActivityOfferingTypesToFormatOffering(FormatOfferingInfo fo, CourseInfo course,
390                                                            TypeService typeService, ContextInfo context)
391             throws PermissionDeniedException, MissingParameterException, InvalidParameterException, OperationFailedException,
392             DoesNotExistException {
393         if (fo.getActivityOfferingTypeKeys() != null && !fo.getActivityOfferingTypeKeys().isEmpty()) {
394             // Only bother with this if there are no AO type keys
395             return;
396         }
397         List<FormatInfo> formats = course.getFormats();
398         FormatInfo format = null;
399         for (FormatInfo f: formats) {
400             if (f.getId().equals(fo.getFormatId())) {
401                 // Find correct format
402                 format = f;
403                 break;
404             }
405         }
406         // Get the activity types
407         List<String> activityTypes = new ArrayList<String>();
408         for (ActivityInfo activityInfo: format.getActivities()) {
409             activityTypes.add(activityInfo.getTypeKey());
410         }
411         // Use type service to find corresponding AO types--assumes 1-1 mapping of Activity types to AO types
412         List<String> aoTypeKeys = new ArrayList<String>();
413         for (String activityType: activityTypes) {
414             List<TypeTypeRelationInfo> typeTypeRels =
415                     typeService.getTypeTypeRelationsByOwnerAndType(activityType,
416                             TypeServiceConstants.TYPE_TYPE_RELATION_ALLOWED_TYPE_KEY,
417                             context);
418             if (typeTypeRels.size() != 1) {
419                 // Ref data currently only has a 1-1 mapping between Activity types (CLU) and AO types (LUI)
420                 // The UI screens only support this.  Should there be a many-to-1 relation between AO types and Activity
421                 // types (as they were originally envisioned), then this exception will be thrown.
422                 throw new UnsupportedOperationException("Can't handle Activity Type -> AO Type that isn't 1-1.  Search for this message in Java code");
423             } else {
424                 String aoType = typeTypeRels.get(0).getRelatedTypeKey();
425                 aoTypeKeys.add(aoType);
426             }
427         }
428         // Finally, set the ao types for this fo
429         fo.setActivityOfferingTypeKeys(aoTypeKeys);
430     }
431 
432     /**
433      * Returns the day-of-the-year represented by the supplied term.
434      *
435      * @param termInfo
436      * @param academicCalendarService
437      * @param contextInfo
438      * @return int of the day-of-the-year that represents the term
439      * @throws DoesNotExistException
440      * @throws InvalidParameterException
441      * @throws MissingParameterException
442      * @throws OperationFailedException
443      * @throws PermissionDeniedException
444      */
445     public static int calculateTermDayOfYear( TermInfo termInfo, AcademicCalendarService academicCalendarService, ContextInfo contextInfo )
446         throws DoesNotExistException, InvalidParameterException, MissingParameterException, OperationFailedException, PermissionDeniedException {
447 
448         int termDayOfYear = 0; // default to 1st day of the year
449 
450         List<KeyDateInfo> keyDateInfoList = academicCalendarService.getKeyDatesForTerm( termInfo.getId(), contextInfo );
451         for(KeyDateInfo keyDateInfo : keyDateInfoList ) {
452             if( keyDateInfo.getTypeKey().equalsIgnoreCase(AtpServiceConstants.MILESTONE_INSTRUCTIONAL_PERIOD_TYPE_KEY)
453                     && keyDateInfo.getStartDate() != null
454                     && keyDateInfo.getEndDate() != null )
455             {
456                 Date termClassStartDate = keyDateInfo.getStartDate();
457                 Date termClassEndDate = keyDateInfo.getEndDate();
458                 Date avgDate = new Date( termClassStartDate.getTime() + ( (termClassEndDate.getTime() - termClassStartDate.getTime()) /2 ) );
459                 Calendar cal = Calendar.getInstance();
460                 cal.setTime( avgDate) ;
461                 termDayOfYear = cal.get( Calendar.DAY_OF_YEAR );
462                 break;
463             }
464         }
465 
466         return termDayOfYear;
467     }
468 
469 
470     public static List<CourseOfferingInfo> loadCourseOfferings( SearchService searchService, SearchRequestInfo searchRequest ) throws InvalidParameterException, MissingParameterException, PermissionDeniedException, OperationFailedException {
471 
472         List<CourseOfferingInfo> result = new ArrayList<CourseOfferingInfo>();
473 
474         ContextInfo contextInfo = new ContextInfo();
475 
476         SearchResultInfo searchResult = searchService.search( searchRequest, contextInfo );
477 
478         for( SearchResultRowInfo row : searchResult.getRows() ) {
479             CourseOfferingInfo courseOfferingInfo = new CourseOfferingInfo();
480 
481             for( SearchResultCellInfo cellInfo : row.getCells() ) {
482 
483                 String value = StringUtils.defaultIfEmpty( cellInfo.getValue(), StringUtils.EMPTY );
484 
485                 if( CourseOfferingManagementSearchImpl.SearchResultColumns.CODE.equals( cellInfo.getKey() ) ) {
486                     courseOfferingInfo.setCourseOfferingCode( value );
487                 }
488                 else if( CourseOfferingManagementSearchImpl.SearchResultColumns.CO_ID.equals( cellInfo.getKey() ) ) {
489                     courseOfferingInfo.setId( value );
490                 }
491 
492             }
493 
494             result.add( courseOfferingInfo );
495         }
496 
497         return result;
498     }
499 
500 }