org.kuali.coeus.common.committee.impl.meeting.MeetingServiceImplBase.java Source code

Java tutorial

Introduction

Here is the source code for org.kuali.coeus.common.committee.impl.meeting.MeetingServiceImplBase.java

Source

/*
 * Kuali Coeus, a comprehensive research administration system for higher education.
 * 
 * Copyright 2005-2015 Kuali, Inc.
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 * 
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.kuali.coeus.common.committee.impl.meeting;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.kuali.coeus.common.committee.impl.bo.CommitteeBase;
import org.kuali.coeus.common.committee.impl.bo.CommitteeMembershipBase;
import org.kuali.coeus.common.committee.impl.bo.CommitteeMembershipRole;
import org.kuali.coeus.common.committee.impl.bo.CommitteeScheduleBase;
import org.kuali.coeus.common.committee.impl.web.struts.form.schedule.Time12HrFmt;
import org.kuali.kra.protocol.actions.submit.ProtocolSubmissionLiteBase;
import org.kuali.kra.protocol.correspondence.ProtocolCorrespondence;
import org.kuali.rice.core.api.datetime.DateTimeService;
import org.kuali.rice.core.api.util.RiceKeyConstants;
import org.kuali.rice.kns.util.KNSGlobalVariables;
import org.kuali.rice.krad.bo.PersistableBusinessObject;
import org.kuali.rice.krad.service.BusinessObjectService;
import org.kuali.rice.krad.service.SequenceAccessorService;
import org.kuali.rice.krad.util.GlobalVariables;
import org.springframework.transaction.annotation.Transactional;

import java.sql.Date;
import java.sql.Timestamp;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;

@SuppressWarnings("deprecation")
@Transactional
public abstract class MeetingServiceImplBase<CS extends CommitteeScheduleBase<CS, CMT, ?, CSM>, CSM extends CommitteeScheduleMinuteBase<CSM, CS>, CMT extends CommitteeBase<CMT, ?, CS>>

        implements CommonMeetingService<CS, CSM> {

    BusinessObjectService businessObjectService;

    SequenceAccessorService sequenceAccessorService;

    DateTimeService dateTimeService;

    private static final String PROTOCOL_SUBMISSIONS_REF_ID = "protocolSubmissions";
    private static final String COMMITTEE_SCHEDULE_MINUTES_REF_ID = "committeeScheduleMinutes";

    /*
     * 
     * This method is to get the last agenda generation date.
     * 
     * @param scheduleId
     * 
     * @return
     */
    protected Date getAgendaGenerationDate(Long scheduleId) {
        List<ScheduleAgendaBase> scheduleAgendas = getAgenda(scheduleId);
        if (scheduleAgendas.isEmpty()) {
            return null;
        } else {
            return new Date(scheduleAgendas.get(scheduleAgendas.size() - 1).getCreateTimestamp().getTime());
        }
    }

    /*
     * This method is to get the generated agenda for this committee schedule.
     */
    protected List<ScheduleAgendaBase> getAgenda(Long scheduleId) {
        return (List<ScheduleAgendaBase>) businessObjectService.findMatchingOrderBy(getScheduleAgendaBOClassHook(),
                Collections.singletonMap("scheduleIdFk", scheduleId), "createTimestamp", true);
    }

    protected abstract Class<? extends ScheduleAgendaBase> getScheduleAgendaBOClassHook();

    /*
     * This method is get the meeting minute documents of the selected committee schedule
     */
    protected List<CommScheduleMinuteDocBase> getMinuteDoc(Long scheduleId) {
        return (List<CommScheduleMinuteDocBase>) businessObjectService.findMatchingOrderBy(
                getCommScheduleMinuteDocBOClassHook(), Collections.singletonMap("scheduleIdFk", scheduleId),
                "createTimestamp", true);
    }

    protected abstract Class<? extends CommScheduleMinuteDocBase> getCommScheduleMinuteDocBOClassHook();

    /*
     * This method is to get all protocol correspondences of the protocols that are related
     * to this committee schedule. ie, protocols that have been submitted to this committee schedule.
     */
    protected List<ProtocolCorrespondence> getCorrespondences(CS committeeSchedule) {
        Map<String, Long> fieldValues = new HashMap<>();
        List<Long> protocolIds = new ArrayList<>();
        List<ProtocolCorrespondence> correspondences = new ArrayList<>();
        // TODO : check if want to use criteria/dao to get the list or use this loop
        for (ProtocolSubmissionLiteBase submission : committeeSchedule.getLatestProtocolSubmissions()) {
            if (!protocolIds.contains(submission.getProtocolId())) {
                protocolIds.add(submission.getProtocolId());
                fieldValues.put("protocolId", submission.getProtocolId());

                correspondences.addAll(
                        businessObjectService.findMatching(getProtocolCorrespondenceBOClassHook(), fieldValues));
            }
        }
        return correspondences;
    }

    protected abstract Class<? extends ProtocolCorrespondence> getProtocolCorrespondenceBOClassHook();

    @Override
    public void saveMeetingDetails(CS committeeSchedule, List<? extends PersistableBusinessObject> deletedBos) {
        committeeSchedule.setStartTime(addHrMinToDate(committeeSchedule.getViewStartTime()));
        committeeSchedule.setEndTime(addHrMinToDate(committeeSchedule.getViewEndTime()));
        committeeSchedule.setTime(addHrMinToDate(committeeSchedule.getViewTime()));

        if (!deletedBos.isEmpty()) {
            businessObjectService.delete(deletedBos);
        }

        refreshAndSaveSchedule(committeeSchedule);

        // display "successfully saved" message
        KNSGlobalVariables.getMessageList().add(RiceKeyConstants.MESSAGE_SAVED);

    }

    @Override
    public String getStandardReviewComment(String protocolContingencyCode) {
        String description = null;
        Map<String, String> queryMap = new HashMap<>();
        queryMap.put("protocolContingencyCode", protocolContingencyCode);
        ProtocolContingencyBase protocolContingency = businessObjectService
                .findByPrimaryKey(getProtocolContingencyBOClassHook(), queryMap);
        if (protocolContingency != null) {
            description = protocolContingency.getDescription();
        }
        return description;
    }

    protected abstract Class<? extends ProtocolContingencyBase> getProtocolContingencyBOClassHook();

    /*
     * utility methods by adding minutes to date
     */
    protected Timestamp addHrMinToDate(Time12HrFmt viewTime) {
        java.util.Date dt = new java.util.Date(0); // this is actually 12-31-1969 19:00.  its GMT time
        DateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy h:mm a");
        try {
            // support localization. 
            dt = dateFormat.parse("01/01/1970 " + viewTime.getTime() + " " + viewTime.getMeridiem());
            return new Timestamp(dt.getTime());
        } catch (Exception e) {
            // TODO : not sure to throw runtimeexception or not.
            // folowing may convert date to 07-02-1970 iftz is gmt+12 or more
            dt = DateUtils.round(dt, Calendar.DAY_OF_MONTH);
            return new Timestamp(DateUtils.addMinutes(dt, viewTime.findMinutes()).getTime());

        }
    }

    public void setBusinessObjectService(BusinessObjectService businessObjectService) {
        this.businessObjectService = businessObjectService;
    }

    public void setSequenceAccessorService(SequenceAccessorService sequenceAccessorService) {
        this.sequenceAccessorService = sequenceAccessorService;
    }

    public void setDateTimeService(DateTimeService dateTimeService) {
        this.dateTimeService = dateTimeService;
    }

    /**
     * 
     * @see org.kuali.coeus.common.committee.impl.meeting.CommonMeetingService#addOtherAction(org.kuali.coeus.common.committee.impl.meeting.CommScheduleActItemBase,
     *      org.kuali.coeus.common.committee.impl.bo.CommitteeScheduleBase)
     */
    public void addOtherAction(CommScheduleActItemBase newOtherAction, CS committeeSchedule) {
        newOtherAction.refreshReferenceObject("scheduleActItemType");
        newOtherAction.setCommScheduleActItemsId(getNextCommScheduleActItemId(newOtherAction.getClass()));
        newOtherAction.setScheduleIdFk(committeeSchedule.getId());
        newOtherAction.setActionItemNumber(getNextActionItemNumber(committeeSchedule));
        committeeSchedule.getCommScheduleActItems().add(newOtherAction);
    }

    protected Long getNextCommScheduleActItemId(Class boClass) {
        return sequenceAccessorService.getNextAvailableSequenceNumber("SEQ_MEETING_ID", boClass);
    }

    /*
     * find the max action number and increase by one.
     */
    protected Integer getNextActionItemNumber(CS committeeSchedule) {
        Integer nextActionItemNumber = committeeSchedule.getCommScheduleActItems().size();
        for (CommScheduleActItemBase commScheduleActItem : committeeSchedule.getCommScheduleActItems()) {
            if (commScheduleActItem.getActionItemNumber() > nextActionItemNumber) {
                nextActionItemNumber = commScheduleActItem.getActionItemNumber();
            }
        }
        return nextActionItemNumber + 1;

    }

    /**
     * 
     * @see org.kuali.coeus.common.committee.impl.meeting.CommonMeetingService#deleteOtherAction(org.kuali.coeus.common.committee.impl.bo.CommitteeScheduleBase, int,
     *      java.util.List)
     */
    public void deleteOtherAction(CS committeeSchedule, int itemNumber,
            List<CommScheduleActItemBase> deletedOtherActions) {
        CommScheduleActItemBase commScheduleActItem = committeeSchedule.getCommScheduleActItems().get(itemNumber);
        if (commScheduleActItem.getCommScheduleActItemsId() != null) {
            deletedOtherActions.add(commScheduleActItem);
        }
        committeeSchedule.getCommScheduleActItems().remove(itemNumber);

    }

    @Override
    public void markAbsent(List<MemberPresentBean> memberPresentBeans, List<MemberAbsentBean> memberAbsentBeans,
            int itemNumber) {
        MemberPresentBean memberPresentBean = memberPresentBeans.get(itemNumber);
        MemberAbsentBean memberAbsentBean = new MemberAbsentBean();
        memberPresentBean.getAttendance().setAlternateFor(null);
        memberAbsentBean.setAttendance(memberPresentBean.getAttendance());
        memberAbsentBeans.add(memberAbsentBean);
        memberPresentBeans.remove(itemNumber);
    }

    @Override
    public void presentVoting(MeetingHelperBase meetingHelper, int itemNumber) {
        MemberAbsentBean memberAbsentBean = meetingHelper.getMemberAbsentBeans().get(itemNumber);
        MemberPresentBean memberPresentBean = new MemberPresentBean();
        memberPresentBean.setAttendance(memberAbsentBean.getAttendance());
        memberPresentBean.getAttendance()
                .setAlternateFlag(isAlternateForMember((CS) meetingHelper.getCommitteeSchedule(),
                        memberPresentBean.getAttendance(),
                        meetingHelper.getCommitteeSchedule().getScheduledDate()));
        meetingHelper.getMemberPresentBeans().add(memberPresentBean);
        meetingHelper.getMemberAbsentBeans().remove(itemNumber);
    }

    /*
     * This is a utility method to reset alternate flag before 'present voting'
     */
    protected boolean isAlternateForMember(CS commonCommitteeSchedule,
            CommitteeScheduleAttendanceBase committeeScheduleAttendance, Date scheduledDate) {
        boolean isAlternate = false;
        for (CommitteeMembershipBase committeeMembership : commonCommitteeSchedule.getParentCommittee()
                .getCommitteeMemberships()) {
            if ((committeeScheduleAttendance.getNonEmployeeFlag() && committeeMembership.getRolodexId() != null
                    && committeeScheduleAttendance.getPersonId()
                            .equals(committeeMembership.getRolodexId().toString()))
                    || (!committeeScheduleAttendance.getNonEmployeeFlag() && committeeScheduleAttendance
                            .getPersonId().equals(committeeMembership.getPersonId()))) {
                if (isActiveMembership(committeeMembership, scheduledDate)
                        && isAlternate(committeeMembership, scheduledDate)) {
                    isAlternate = true;
                    break;
                }
            }
        }
        return isAlternate;
    }

    protected boolean isActiveMembership(CommitteeMembershipBase committeeMembership, Date scheduledDate) {
        return isActiveForScheduledDate(scheduledDate, committeeMembership.getTermStartDate(),
                committeeMembership.getTermEndDate())
                && hasActiveMembershipRoleForScheduledDate(committeeMembership.getMembershipRoles(), scheduledDate);
    }

    private boolean hasActiveMembershipRoleForScheduledDate(List<CommitteeMembershipRole> committeeMembershipRoles,
            Date scheduledDate) {
        for (CommitteeMembershipRole membershipRole : committeeMembershipRoles) {
            if (!membershipRole.getMembershipRoleCode().equals(CommitteeMembershipRole.INACTIVE_ROLE)
                    && isActiveForScheduledDate(scheduledDate, membershipRole.getStartDate(),
                            membershipRole.getEndDate())) {
                return true;
            }
        }
        return false;
    }

    private boolean isActiveForScheduledDate(Date scheduledDate, Date startDate, Date endDate) {
        return startDate.before(scheduledDate) && endDate.after(scheduledDate);
    }

    /*
     * check if this membership has alternate role based on schedule date.
     */
    protected boolean isAlternate(CommitteeMembershipBase committeeMembership, Date scheduledDate) {
        boolean isAlternate = false;
        for (CommitteeMembershipRole membershipRole : committeeMembership.getMembershipRoles()) {
            if (membershipRole.getMembershipRoleCode().equals(CommitteeMembershipRole.ALTERNATE_ROLE)
                    && !membershipRole.getStartDate().after(scheduledDate)
                    && !membershipRole.getEndDate().before(scheduledDate)) {
                isAlternate = true;
                break;
            }
        }
        return isAlternate;
    }

    /*
     * get a person's role name within this committee memberships based on schedule date.
     */
    protected void getRoleName(CommitteeScheduleAttendanceBase committeeScheduleAttendance,
            List<CommitteeMembershipBase> committeeMemberships, Date scheduleDate) {
        String roleName = "";
        for (CommitteeMembershipBase committeeMembership : committeeMemberships) {
            if ((committeeScheduleAttendance.getNonEmployeeFlag() && committeeMembership.getRolodexId() != null
                    && committeeScheduleAttendance.getPersonId()
                            .equals(committeeMembership.getRolodexId().toString()))
                    || (!committeeScheduleAttendance.getNonEmployeeFlag() && committeeScheduleAttendance
                            .getPersonId().equals(committeeMembership.getPersonId()))) {
                roleName = getRoleNameForMembership(committeeMembership, scheduleDate);
                break;
            }
        }
        committeeScheduleAttendance.setRoleName(roleName);
    }

    /*
     * get rolename, concatenated with ',' separator if multiple roles exist for this membership
     */
    protected String getRoleNameForMembership(CommitteeMembershipBase committeeMembership, Date scheduledDate) {
        String roleName = "";
        for (CommitteeMembershipRole membershipRole : committeeMembership.getMembershipRoles()) {
            if (isActiveForScheduledDate(scheduledDate, membershipRole.getStartDate(),
                    membershipRole.getEndDate())) {
                roleName = roleName + "," + membershipRole.getMembershipRole().getDescription();
            }
        }
        if (StringUtils.isNotBlank(roleName)) {
            roleName = roleName.substring(1); // remove ","
        }
        return roleName;
    }

    /*
     * Check if this member is active in this committee. Inactive scenario : - not defined in membership. - in membership, but non
     * of the memberships period cover schedule date - an 'Inactive' role period cover schedule date.
     */
    protected boolean isActiveMember(CommitteeScheduleAttendanceBase committeeScheduleAttendance,
            List<CommitteeMembershipBase> committeeMemberships, Date scheduleDate) {
        boolean isActiveMember = false;
        for (CommitteeMembershipBase committeeMembership : committeeMemberships) {
            if ((committeeScheduleAttendance.getNonEmployeeFlag() && committeeMembership.getRolodexId() != null
                    && committeeScheduleAttendance.getPersonId()
                            .equals(committeeMembership.getRolodexId().toString()))
                    || (!committeeScheduleAttendance.getNonEmployeeFlag() && committeeScheduleAttendance
                            .getPersonId().equals(committeeMembership.getPersonId()))) {
                if (isActiveForScheduledDate(scheduleDate, committeeMembership.getTermStartDate(),
                        committeeMembership.getTermEndDate())) {
                    isActiveMember = isActiveMembership(committeeMembership, scheduleDate);
                }
            }
        }
        return isActiveMember;
    }

    protected boolean isActiveMemberAbsent(CommitteeScheduleAttendanceBase committeeScheduleAttendance,
            CommitteeMembershipBase committeeMembership, Date scheduleDate) {
        boolean isActiveMember = false;
        if ((committeeScheduleAttendance.getNonEmployeeFlag() && committeeMembership.getRolodexId() != null
                && committeeScheduleAttendance.getPersonId().equals(committeeMembership.getRolodexId().toString()))
                || (!committeeScheduleAttendance.getNonEmployeeFlag()
                        && committeeScheduleAttendance.getPersonId() != null
                        && committeeScheduleAttendance.getPersonId().equals(committeeMembership.getPersonId()))) {
            if (!committeeMembership.getTermStartDate().after(scheduleDate)
                    && !committeeMembership.getTermEndDate().before(scheduleDate)) {
                isActiveMember = isActiveMembership(committeeMembership, scheduleDate);
            }
        }

        return isActiveMember;
    }

    @Override
    public void presentOther(MeetingHelperBase meetingHelper, int itemNumber) {
        MemberAbsentBean memberAbsentBean = meetingHelper.getMemberAbsentBeans().get(itemNumber);
        OtherPresentBeanBase otherPresentBean = getNewOtherPresentBeanInstanceHook();
        otherPresentBean.setAttendance(memberAbsentBean.getAttendance());
        otherPresentBean.setMember(true);
        meetingHelper.getOtherPresentBeans().add(otherPresentBean);
        meetingHelper.getMemberAbsentBeans().remove(itemNumber);
    }

    protected abstract OtherPresentBeanBase getNewOtherPresentBeanInstanceHook();

    @Override
    public void addOtherPresent(MeetingHelperBase meetingHelper) {
        OtherPresentBeanBase otherPresentBean = getNewOtherPresentBeanInstanceHook();
        otherPresentBean.setMember(false);
        meetingHelper.getNewOtherPresentBean().getAttendance().setRoleName("Guest");
        otherPresentBean.setAttendance(meetingHelper.getNewOtherPresentBean().getAttendance());
        memberHandling(meetingHelper, otherPresentBean);
        meetingHelper.getOtherPresentBeans().add(otherPresentBean);
        meetingHelper.setNewOtherPresentBean(getNewOtherPresentBeanInstanceHook());
    }

    /**
     * 
     * This method is called when 'addOtherPresent'. It is to check if the selected person/rolodex is in member absent. if the
     * selected person/rolodex is in memberabsentbean list, then removed it from the membersentbean list; so this person will not be
     * displayed in member absent panel.
     */
    protected void memberHandling(MeetingHelperBase meetingHelper, OtherPresentBeanBase otherPresentBean) {
        MemberAbsentBean matchedMemberAbsentBean = null;
        for (MemberAbsentBean memberAbsentBean : meetingHelper.getMemberAbsentBeans()) {
            if (isAbsentMember(memberAbsentBean, otherPresentBean)) {
                otherPresentBean.setMember(true);
                getRoleName(otherPresentBean.getAttendance(),
                        meetingHelper.getCommitteeSchedule().getParentCommittee().getCommitteeMemberships(),
                        meetingHelper.getCommitteeSchedule().getScheduledDate());
                matchedMemberAbsentBean = memberAbsentBean;
            }
        }
        if (matchedMemberAbsentBean != null) {
            meetingHelper.getMemberAbsentBeans().remove(matchedMemberAbsentBean);
        }

    }

    /*
     * This method checks if the person/rolodex is an absent member.
     */
    protected boolean isAbsentMember(MemberAbsentBean memberAbsentBean, OtherPresentBeanBase otherPresentBean) {
        boolean isPresent = false;
        if (memberAbsentBean.getAttendance().getNonEmployeeFlag()
                && otherPresentBean.getAttendance().getNonEmployeeFlag() && memberAbsentBean.getAttendance()
                        .getPersonId().equals(otherPresentBean.getAttendance().getPersonId())) {
            isPresent = true;
        } else if (!memberAbsentBean.getAttendance().getNonEmployeeFlag()
                && !otherPresentBean.getAttendance().getNonEmployeeFlag() && memberAbsentBean.getAttendance()
                        .getPersonId().equals(otherPresentBean.getAttendance().getPersonId())) {
            isPresent = true;
        }
        return isPresent;
    }

    @Override
    public void deleteOtherPresent(MeetingHelperBase meetingHelper, int itemNumber) {
        OtherPresentBeanBase otherPresentBean = meetingHelper.getOtherPresentBeans().get(itemNumber);
        if (otherPresentBean.isMember()) {
            MemberAbsentBean memberAbsentBean = new MemberAbsentBean();
            memberAbsentBean.setAttendance(otherPresentBean.getAttendance());
            meetingHelper.getMemberAbsentBeans().add(memberAbsentBean);
        }
        meetingHelper.getOtherPresentBeans().remove(itemNumber);
    }

    @Override
    public void addCommitteeScheduleMinute(MeetingHelperBase meetingHelper) {
        meetingHelper.getNewCommitteeScheduleMinute().refreshReferenceObject("minuteEntryType");
        meetingHelper.getNewCommitteeScheduleMinute().refreshReferenceObject("protocol");
        meetingHelper.getNewCommitteeScheduleMinute().refreshReferenceObject("commScheduleActItem");

        Long submissionId = null;
        if (meetingHelper.getNewCommitteeScheduleMinute().getProtocol() != null) {
            submissionId = meetingHelper.getNewCommitteeScheduleMinute().getProtocol().getProtocolSubmission()
                    .getSubmissionId();
        }
        Long scheduleId = meetingHelper.getCommitteeSchedule().getId();
        Integer entryNumber = getNextMinuteEntryNumber((CS) meetingHelper.getCommitteeSchedule());
        String principalName = GlobalVariables.getUserSession().getPrincipalName();
        String minuteEntryTypeCode = meetingHelper.getNewCommitteeScheduleMinute().getMinuteEntryTypeCode();
        Timestamp createTimestamp = dateTimeService.getCurrentTimestamp();

        meetingHelper.getNewCommitteeScheduleMinute().setSubmissionIdFk(submissionId);
        meetingHelper.getNewCommitteeScheduleMinute().setScheduleIdFk(scheduleId);
        meetingHelper.getNewCommitteeScheduleMinute().setEntryNumber(entryNumber);
        meetingHelper.getNewCommitteeScheduleMinute().setCreateUser(principalName);
        meetingHelper.getNewCommitteeScheduleMinute().setUpdateUser(principalName);
        meetingHelper.getNewCommitteeScheduleMinute().setCreateTimestamp(createTimestamp);
        // set this up for display when 'add'
        meetingHelper.getNewCommitteeScheduleMinute().setUpdateTimestamp(createTimestamp);

        if (MinuteEntryType.ATTENDANCE.equals(minuteEntryTypeCode)) {
            addAttendanceMinuteEntry(meetingHelper);
        } else if (MinuteEntryType.ACTION_ITEM.equals(minuteEntryTypeCode)) {
            addActionItem(meetingHelper);
        } else if (MinuteEntryType.PROTOCOL.equals(minuteEntryTypeCode)) {
            resetActionItemFields(meetingHelper);
        } else {
            resetProtocolFields(meetingHelper);
            resetActionItemFields(meetingHelper);
        }

        ((List<CSM>) (meetingHelper.getCommitteeSchedule().getCommitteeScheduleMinutes()))
                .add((CSM) meetingHelper.getNewCommitteeScheduleMinute());
        meetingHelper.setNewCommitteeScheduleMinute(getNewCommitteeScheduleMinuteInstanceHook());
    }

    protected abstract CSM getNewCommitteeScheduleMinuteInstanceHook();

    /*
     * Utility method to figure out next entry number for this schedule.
     */
    protected Integer getNextMinuteEntryNumber(CS commonCommitteeSchedule) {
        Integer nextMinuteEntryNumber = commonCommitteeSchedule.getCommitteeScheduleMinutes().size();
        for (CSM committeeScheduleMinute : commonCommitteeSchedule.getCommitteeScheduleMinutes()) {
            if (committeeScheduleMinute.getEntryNumber() > nextMinuteEntryNumber) {
                nextMinuteEntryNumber = committeeScheduleMinute.getEntryNumber();
            }
        }
        return nextMinuteEntryNumber + 1;
    }

    /*
     * Adds a minute entry to an attendance minute type
     */
    protected void addAttendanceMinuteEntry(MeetingHelperBase meetingHelper) {
        if (meetingHelper.getNewCommitteeScheduleMinute().isGenerateAttendance() && meetingHelper.isJsDisabled()) {
            // in case JS is disabled
            meetingHelper.getNewCommitteeScheduleMinute()
                    .setMinuteEntry(generateAttendanceComment(meetingHelper.getMemberPresentBeans(),
                            meetingHelper.getOtherPresentBeans(), (CS) meetingHelper.getCommitteeSchedule()));
        }
        resetProtocolFields(meetingHelper);
        resetActionItemFields(meetingHelper);
    }

    /*
     * This is to generate comment for minute entry Type of 'Attendance' and 'generate attendance is checked
     */
    protected String generateAttendanceComment(List<MemberPresentBean> memberPresentBeans,
            List<OtherPresentBeanBase> otherPresentBeans, CS commonCommitteeSchedule) {
        String comment = "";
        String eol = System.getProperty("line.separator");
        for (MemberPresentBean memberPresentBean : memberPresentBeans) {
            if (StringUtils.isNotBlank(comment)) {
                comment = comment + eol;
            }
            comment = comment + memberPresentBean.getAttendance().getPersonName();
            if (StringUtils.isNotBlank(memberPresentBean.getAttendance().getAlternateFor())) {
                comment = comment + " Alternate For: " + getAlternateForName(commonCommitteeSchedule,
                        memberPresentBean.getAttendance().getAlternateFor());
            }
        }
        for (OtherPresentBeanBase otherPresentBean : otherPresentBeans) {
            if (StringUtils.isNotBlank(comment)) {
                comment = comment + eol;
            }
            comment = comment + otherPresentBean.getAttendance().getPersonName() + " Guest ";
        }
        return comment;
    }

    /*
     * Utility to get person name for 'alternate for'. This name is used when 'generate attendance' is checked.
     */
    protected String getAlternateForName(CS commonCommitteeSchedule, String alternateFor) {

        String personName = "";
        for (CommitteeMembershipBase committeeMembership : commonCommitteeSchedule.getParentCommittee()
                .getCommitteeMemberships()) {
            if ((StringUtils.isNotBlank(committeeMembership.getPersonId())
                    && committeeMembership.getPersonId().equals(alternateFor))
                    || (StringUtils.isBlank(committeeMembership.getPersonId())
                            && committeeMembership.getRolodexId().toString().equals(alternateFor))) {
                personName = committeeMembership.getPersonName();
                break;
            }
        }
        return personName;
    }

    /*
     * Adds an action item to an action item minute type
     */
    protected void addActionItem(MeetingHelperBase meetingHelper) {
        if (meetingHelper.getNewCommitteeScheduleMinute().getCommScheduleActItemsIdFk() != null) {
            // in case adding non-persisted action item
            meetingHelper.getNewCommitteeScheduleMinute()
                    .setCommScheduleActItem(getActionItem(
                            meetingHelper.getNewCommitteeScheduleMinute().getCommScheduleActItemsIdFk(),
                            meetingHelper.getCommitteeSchedule().getCommScheduleActItems()));
        }
        resetProtocolFields(meetingHelper);
    }

    /*
     * Empties protocol fields if the minute type is not PROTOCOL
     */
    protected void resetProtocolFields(MeetingHelperBase meetingHelper) {
        meetingHelper.getNewCommitteeScheduleMinute().setProtocolIdFk(null);
        meetingHelper.getNewCommitteeScheduleMinute().setProtocol(null);
    }

    /*
     * Empties action item fields if the minute type is not ACTION_ITEM
     */
    protected void resetActionItemFields(MeetingHelperBase meetingHelper) {
        meetingHelper.getNewCommitteeScheduleMinute().setCommScheduleActItemsIdFk(null);
        meetingHelper.getNewCommitteeScheduleMinute().setCommScheduleActItem(null);
    }

    /*
     * Utility to find a CommScheduleActItemBase with ID commScheduleActItemsIdFk
     */
    protected CommScheduleActItemBase getActionItem(Long commScheduleActItemsIdFk,
            List<CommScheduleActItemBase> commScheduleActItems) {
        CommScheduleActItemBase actionItem = null;

        for (CommScheduleActItemBase commScheduleActItem : commScheduleActItems) {
            if (commScheduleActItem.getCommScheduleActItemsId().equals(commScheduleActItemsIdFk)) {
                actionItem = commScheduleActItem;
                break;
            }
        }

        return actionItem;
    }

    /**
     * 
     * @see org.kuali.coeus.common.committee.impl.meeting.CommonMeetingService#deleteCommitteeScheduleMinute(org.kuali.coeus.common.committee.impl.bo.CommitteeScheduleBase,
     *      java.util.List, int)
     */
    public void deleteCommitteeScheduleMinute(CS committeeSchedule, List<CSM> deletedCommitteeScheduleMinutes,
            int itemNumber) {
        CSM committeeScheduleMinute = committeeSchedule.getCommitteeScheduleMinutes().get(itemNumber);
        if (committeeScheduleMinute.getCommScheduleMinutesId() != null) {
            deletedCommitteeScheduleMinutes.add(committeeScheduleMinute);
        }
        committeeSchedule.getCommitteeScheduleMinutes().remove(itemNumber);
    }

    /**
     *
     * @see org.kuali.coeus.common.committee.impl.meeting.CommonMeetingService#moveupCommitteeScheduleMinute(org.kuali.coeus.common.committee.impl.bo.CommitteeScheduleBase, int)
     */
    public void moveupCommitteeScheduleMinute(CS committeeSchedule, int itemNumber) {
        CSM minuteBeingMoved = committeeSchedule.getCommitteeScheduleMinutes().get(itemNumber);
        CSM minuteAbove = committeeSchedule.getCommitteeScheduleMinutes().get(itemNumber - 1);
        if (minuteBeingMoved != null && minuteAbove != null) {
            // swap entry numbers so they will sort correctly
            Integer temp = minuteAbove.getEntryNumber();
            minuteAbove.setEntryNumber(minuteBeingMoved.getEntryNumber());
            minuteBeingMoved.setEntryNumber(temp);
        }
    }

    /**
     *
     * @see org.kuali.coeus.common.committee.impl.meeting.CommonMeetingService#movedownCommitteeScheduleMinute(org.kuali.coeus.common.committee.impl.bo.CommitteeScheduleBase, int)
     */
    public void movedownCommitteeScheduleMinute(CS committeeSchedule, int itemNumber) {
        CSM minuteBeingMoved = committeeSchedule.getCommitteeScheduleMinutes().get(itemNumber);
        CSM minuteBelow = committeeSchedule.getCommitteeScheduleMinutes().get(itemNumber + 1);
        if (minuteBeingMoved != null && minuteBelow != null) {
            // swap entry numbers so they will sort correctly
            Integer temp = minuteBelow.getEntryNumber();
            minuteBelow.setEntryNumber(minuteBeingMoved.getEntryNumber());
            minuteBeingMoved.setEntryNumber(temp);
        }
    }

    /**
     * 
     * @see org.kuali.coeus.common.committee.impl.meeting.CommonMeetingService#populateFormHelper(org.kuali.coeus.common.committee.impl.meeting.MeetingHelperBase,
     *      org.kuali.coeus.common.committee.impl.bo.CommitteeScheduleBase, int)
     */
    public void populateFormHelper(MeetingHelperBase meetingHelper, CS commSchedule, int lineNumber) {
        for (ProtocolSubmissionLiteBase protocolSubmission : commSchedule.getLatestProtocolSubmissions()) {
            ProtocolSubmittedBean protocolSubmittedBean = new ProtocolSubmittedBean();
            protocolSubmittedBean.setPersonId(protocolSubmission.getPiPersonId());
            protocolSubmittedBean.setPersonName(protocolSubmission.getPiPersonName());
            protocolSubmittedBean.setRolodexId(protocolSubmission.getPiRolodexId());
            meetingHelper.getProtocolSubmittedBeans().add(protocolSubmittedBean);
        }
        if (commSchedule.getCommitteeScheduleAttendances().isEmpty()
                && !commSchedule.getParentCommittee().getCommitteeMemberships().isEmpty()) {
            initAttendance(meetingHelper.getMemberAbsentBeans(), commSchedule);
        } else {
            populateAttendanceToForm(meetingHelper, commSchedule.getParentCommittee().getCommitteeMemberships(),
                    commSchedule);
        }

        meetingHelper.setAgendaGenerationDate(getAgendaGenerationDate(commSchedule.getId()));
        meetingHelper.setCommitteeSchedule(commSchedule);
        meetingHelper.setTabLabel(getMeetingTabTitle((CS) meetingHelper.getCommitteeSchedule(), lineNumber));
        meetingHelper.setScheduleAgendas(getAgenda(commSchedule.getId()));
        meetingHelper.setMinuteDocs(getMinuteDoc(commSchedule.getId()));
        meetingHelper.setCorrespondences(getCorrespondences(commSchedule));
    }

    /*
     * set up title of the first header tab in meeting page. lineNumber is this selected schedule's item number in committee
     * schedule list
     */
    protected String getMeetingTabTitle(CS commonCommitteeSchedule, int lineNumber) {
        DateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy");

        return commonCommitteeSchedule.getParentCommittee().getCommitteeName() + " #" + lineNumber + " Meeting "
                + dateFormat.format(commonCommitteeSchedule.getScheduledDate());

    }

    /*
     * populate 3 attendance form beans
     */
    protected void populateAttendanceToForm(MeetingHelperBase meetingHelper,
            List<CommitteeMembershipBase> committeeMemberships, CS commSchedule) {
        populatePresentBean(meetingHelper, committeeMemberships, commSchedule);
        populateMemberAbsentBean(meetingHelper, committeeMemberships, commSchedule);

    }

    /*
     * populate memberpresentbean &amp; otherpresentbean
     */
    protected void populatePresentBean(MeetingHelperBase meetingHelper,
            List<CommitteeMembershipBase> committeeMemberships, CS commSchedule) {
        meetingHelper.setOtherPresentBeans(new ArrayList<>());
        meetingHelper.setMemberPresentBeans(new ArrayList<>());
        for (CommitteeScheduleAttendanceBase committeeScheduleAttendance : commSchedule
                .getCommitteeScheduleAttendances()) {
            getRoleName(committeeScheduleAttendance, committeeMemberships, commSchedule.getScheduledDate());
            if (committeeScheduleAttendance.getGuestFlag()) {
                OtherPresentBeanBase otherPresentBean = getNewOtherPresentBeanInstanceHook();
                otherPresentBean.setAttendance(committeeScheduleAttendance);
                otherPresentBean.setMember(isActiveMember(committeeScheduleAttendance, committeeMemberships,
                        commSchedule.getScheduledDate()));
                if (StringUtils.isBlank(committeeScheduleAttendance.getRoleName())) {
                    committeeScheduleAttendance.setRoleName("Guest");
                }
                meetingHelper.getOtherPresentBeans().add(otherPresentBean);
                otherPresentBean.setAttendance(committeeScheduleAttendance);
            } else {
                MemberPresentBean memberPresentBean = new MemberPresentBean();
                memberPresentBean.setAttendance(committeeScheduleAttendance);
                meetingHelper.getMemberPresentBeans().add(memberPresentBean);
            }
        }
    }

    /*
     * populate memberabsentbean
     */
    protected void populateMemberAbsentBean(MeetingHelperBase meetingHelper,
            List<CommitteeMembershipBase> committeeMemberships, CS commSchedule) {
        meetingHelper.setMemberAbsentBeans(new ArrayList<>());
        committeeMemberships.forEach(committeeMembership -> {
            if (!isInMemberPresent(meetingHelper.getMemberPresentBeans(), committeeMembership)
                    && !isInOtherPresent(meetingHelper.getOtherPresentBeans(), committeeMembership)) {
                MemberAbsentBean memberAbsentBean = new MemberAbsentBean();

                CommitteeScheduleAttendanceBase attendance = getNewCommitteeScheduleAttendanceInstanceHook();
                attendance.setRoleName(
                        getRoleNameForMembership(committeeMembership, commSchedule.getScheduledDate()));
                if (StringUtils.isBlank(committeeMembership.getPersonId())) {
                    attendance.setPersonId(committeeMembership.getRolodexId().toString());
                } else {
                    attendance.setPersonId(committeeMembership.getPersonId());
                }

                if (isActiveMemberAbsent(attendance, committeeMembership, commSchedule.getScheduledDate())) {
                    attendance.setPersonName(committeeMembership.getPersonName());
                    attendance.setAlternateFlag(false);
                    attendance.setNonEmployeeFlag(StringUtils.isBlank(committeeMembership.getPersonId()));
                    memberAbsentBean.setAttendance(attendance);
                    meetingHelper.getMemberAbsentBeans().add(memberAbsentBean);
                }
            }
        });
    }

    protected abstract CommitteeScheduleAttendanceBase getNewCommitteeScheduleAttendanceInstanceHook();

    /*
     * Init attendance if this meeting schedule is maintained for the first time.
     */
    protected void initAttendance(List<MemberAbsentBean> memberAbsentBeans, CS commSchedule) {
        List<CommitteeMembershipBase> committeeMemberships = commSchedule.getParentCommittee()
                .getCommitteeMemberships();
        committeeMemberships.forEach(committeeMembership -> {
            if (isActiveMembership(committeeMembership, commSchedule.getScheduledDate())) {

                CommitteeScheduleAttendanceBase committeeScheduleAttendance = getNewCommitteeScheduleAttendanceInstanceHook();
                if (StringUtils.isBlank(committeeMembership.getPersonId())) {
                    committeeScheduleAttendance.setPersonId(committeeMembership.getRolodexId().toString());
                    committeeScheduleAttendance.setNonEmployeeFlag(true);
                } else {
                    committeeScheduleAttendance.setPersonId(committeeMembership.getPersonId());
                    committeeScheduleAttendance.setNonEmployeeFlag(false);
                }
                committeeScheduleAttendance.setPersonName(committeeMembership.getPersonName());
                if (isAlternate(committeeMembership, commSchedule.getScheduledDate())) {
                    committeeScheduleAttendance.setAlternateFlag(true);
                } else {
                    committeeScheduleAttendance.setAlternateFlag(false);

                }
                MemberAbsentBean memberAbsentBean = new MemberAbsentBean();
                committeeScheduleAttendance.setRoleName(
                        getRoleNameForMembership(committeeMembership, commSchedule.getScheduledDate()));
                memberAbsentBean.setAttendance(committeeScheduleAttendance);
                memberAbsentBeans.add(memberAbsentBean);
            }
        });
    }

    /*
     * check if person is in member present.
     */
    protected boolean isInMemberPresent(List<MemberPresentBean> memberPresentBeans,
            CommitteeMembershipBase committeeMembership) {
        boolean isPresent = false;
        for (MemberPresentBean memberPresentBean : memberPresentBeans) {
            if (memberPresentBean.getAttendance().getNonEmployeeFlag()
                    && StringUtils.isBlank(committeeMembership.getPersonId()) && memberPresentBean.getAttendance()
                            .getPersonId().equals(committeeMembership.getRolodexId().toString())) {
                isPresent = true;
                break;
            } else if (!memberPresentBean.getAttendance().getNonEmployeeFlag()
                    && StringUtils.isNotBlank(committeeMembership.getPersonId())
                    && memberPresentBean.getAttendance().getPersonId().equals(committeeMembership.getPersonId())) {
                isPresent = true;
                break;
            }
        }
        return isPresent;
    }

    /*
     * check if person is in other present
     */
    protected boolean isInOtherPresent(List<OtherPresentBeanBase> otherPresentBeans,
            CommitteeMembershipBase committeeMembership) {
        boolean isPresent = false;
        for (OtherPresentBeanBase otherPresentBean : otherPresentBeans) {
            if (otherPresentBean.getAttendance().getNonEmployeeFlag()
                    && StringUtils.isBlank(committeeMembership.getPersonId()) && otherPresentBean.getAttendance()
                            .getPersonId().equals(committeeMembership.getRolodexId().toString())) {
                isPresent = true;
                break;
            } else if (!otherPresentBean.getAttendance().getNonEmployeeFlag()
                    && StringUtils.isNotBlank(committeeMembership.getPersonId())
                    && otherPresentBean.getAttendance().getPersonId().equals(committeeMembership.getPersonId())) {
                isPresent = true;
                break;
            }
        }
        return isPresent;
    }

    // this method will refresh the set of protocol submissions and review comments before saving because 
    // they could have been changed asynchronously in a different concurrent user session.
    private void refreshAndSaveSchedule(CS committeeSchedule) {
        // Since a refresh will wipe out all the newly added (unsaved) minutes from the schedule, we will
        // collect all newly added minutes in a separate collection and add them back after the refresh
        List<CSM> preRefreshMinutes = new ArrayList<>();
        // reset entryNumber in each entry because user may have changed ordering
        int nextEntry = 0;
        for (CSM minute : committeeSchedule.getCommitteeScheduleMinutes()) {
            minute.setEntryNumber(nextEntry++);
            preRefreshMinutes.add(minute);
        }

        committeeSchedule.refreshReferenceObject(COMMITTEE_SCHEDULE_MINUTES_REF_ID);
        List<CSM> newlyAddedMinutes = new ArrayList<>();
        for (CSM formMinute : preRefreshMinutes) {
            if (null == formMinute.getCommScheduleMinutesId()) {
                newlyAddedMinutes.add(formMinute);
            } else {
                // look up minute in refreshed list and update it
                for (CSM oldMinute : committeeSchedule.getCommitteeScheduleMinutes()) {
                    if (formMinute.getCommScheduleMinutesId().equals(oldMinute.getCommScheduleMinutesId())) {
                        if (importantChanges(oldMinute, formMinute)) {
                            // only replacing those three fields that are editable
                            oldMinute.setMinuteEntry(formMinute.getMinuteEntry());
                            oldMinute.setFinalFlag(formMinute.isFinal());
                            oldMinute.setPrivateCommentFlag(formMinute.getPrivateCommentFlag());
                            oldMinute.setEntryNumber(formMinute.getEntryNumber());
                            break;
                        }
                    }
                }
            }
        }
        committeeSchedule.getCommitteeScheduleMinutes().addAll(newlyAddedMinutes);
        committeeSchedule.refreshReferenceObject(PROTOCOL_SUBMISSIONS_REF_ID);

        businessObjectService.save(committeeSchedule);
    }

    private boolean importantChanges(CSM oldMinute, CSM newMinute) {
        return oldMinute.getPrivateCommentFlag() != newMinute.getPrivateCommentFlag()
                || oldMinute.isFinal() != newMinute.isFinal()
                || !StringUtils.equals(oldMinute.getMinuteEntry(), newMinute.getMinuteEntry())
                || !oldMinute.getEntryNumber().equals(newMinute.getEntryNumber());
    }
}