/// <summary>
        /// Gets the room occurrence and group data.
        /// </summary>
        /// <param name="dataMap">The data map.</param>
        /// <param name="rockContext">The rock context.</param>
        /// <returns></returns>
        private Dictionary <RoomOccurrence, Group> GetOccurenceAndGroupData(JobDataMap dataMap, RockContext rockContext)
        {
            var result    = new Dictionary <RoomOccurrence, Group>();
            var dates     = GetSearchDates(dataMap);
            var startDate = dates.Min();

            var zRoomOccurrenceService          = new RoomOccurrenceService(rockContext);
            var reservationLocationEntityTypeId = new EntityTypeService(rockContext).GetNoTracking(com.bemaservices.RoomManagement.SystemGuid.EntityType.RESERVATION_LOCATION.AsGuid()).Id;

            // Find all RoomOccurrences that occur on the targeted dates
            var occurrences         = new Dictionary <int, List <DateTime> >();
            var occurrencesToRemind = zRoomOccurrenceService
                                      .Queryable().AsNoTracking()
                                      .Where(ro => ro.EntityTypeId == reservationLocationEntityTypeId &&
                                             ro.ZoomMeetingId > 0 &&
                                             dates.Contains(DbFunctions.TruncateTime(ro.StartTime).Value))
                                      .ToList();

            // Find all Reservation Locations tied to the occurrences that need reminder sent.
            var occurrenceEntityIds        = occurrencesToRemind.Select(o => o.EntityId).ToList();
            var groupService               = new GroupService(rockContext);
            var reservationLocationService = new ReservationLocationService(rockContext);
            var resLocationsToRemind       = reservationLocationService.Queryable("Reservation")
                                             .AsNoTracking()
                                             .Where(rl => occurrenceEntityIds.Contains(rl.Id))
                                             .ToList();

            foreach (var roomOcc in occurrencesToRemind)
            {
                var resLoc = resLocationsToRemind.FirstOrDefault(rl => rl.Id == roomOcc.EntityId);
                resLoc.Reservation.LoadAttributes();
                var groupAttributeValue = resLoc.Reservation.GetAttributeValue(resGroupAttribute.Key);
                if (!string.IsNullOrWhiteSpace(groupAttributeValue))
                {
                    var groupGuid = groupAttributeValue.Split('|')[1].AsGuid();
                    var group     = groupService.Get(groupGuid);
                    if (group != null)
                    {
                        result.Add(roomOcc, group);
                    }
                }
            }
            return(result);
        }
示例#2
0
        /// <summary>
        /// Executes the specified context.
        /// </summary>
        /// <param name="context">The context.</param>
        public virtual void Execute(IJobExecutionContext context)
        {
            // Check Api connection first.
            if (!Zoom.ZoomAuthCheck())
            {
                context.Result = "Zoom API authentication error. Check API settings for Zoom Room plugin or try again later.";
                throw new Exception("Authentication failed for Zoom API. Please verify the API settings configured in the Zoom Room plugin are valid and correct.");
            }

            using (var rockContext = new RockContext())
            {
                #region Setup Variables

                int      jobId                  = context.JobDetail.Description.AsInteger();
                var      job                    = new ServiceJobService(rockContext).GetNoTracking(jobId);
                var      JobStartDateTime       = RockDateTime.Now;
                DateTime?lastSuccessRunDateTime = null;
                if (job != null && job.Guid != Rock.SystemGuid.ServiceJob.JOB_PULSE.AsGuid())
                {
                    lastSuccessRunDateTime = job.LastSuccessfulRunDateTime;
                }

                // get the last run date or yesterday
                var beginDateTime = lastSuccessRunDateTime ?? JobStartDateTime.AddDays(-1);

                var dataMap = context.JobDetail.JobDataMap;
                var daysOut = dataMap.GetIntegerFromString(AttributeKey.SyncDaysOut);
                webhookBaseUrl = Settings.GetWebhookUrl();
                var importMeetings = dataMap.GetBooleanFromString(AttributeKey.ImportMeetings);
                verboseLogging = dataMap.GetBooleanFromString(AttributeKey.VerboseLogging);
                var zrOccurrencesCancel = new List <RoomOccurrence>();
                reservationLocationEntityTypeId = new EntityTypeService(rockContext).GetNoTracking(com.bemaservices.RoomManagement.SystemGuid.EntityType.RESERVATION_LOCATION.AsGuid()).Id;

                var zoom            = Zoom.Api();
                var locationService = new LocationService(rockContext);
                var zrLocations     = locationService.Queryable()
                                      .AsNoTracking()
                                      .WhereAttributeValue(rockContext, a => a.Attribute.Key == "rocks.kfs.ZoomRoom" && a.Value != null && a.Value != "")
                                      .ToList();
                var zrLocationIds           = zrLocations.Select(l => l.Id).ToList();
                var linkedZoomRoomLocations = new Dictionary <int, string>();
                foreach (var loc in zrLocations)
                {
                    loc.LoadAttributes();
                    var zoomRoomDV = DefinedValueCache.Get(loc.GetAttributeValue("rocks.kfs.ZoomRoom").AsGuid());
                    linkedZoomRoomLocations.Add(loc.Id, zoomRoomDV.Value);
                }

                #endregion Setup Variables

                #region Mark Completed Occurrences

                var zrOccurrenceService  = new RoomOccurrenceService(rockContext);
                var completedOccurrences = zrOccurrenceService.Queryable()
                                           .Where(ro => ro.IsCompleted == false &&
                                                  DbFunctions.AddMinutes(ro.StartTime, ro.Duration) < beginDateTime);
                foreach (var occ in completedOccurrences)
                {
                    occ.IsCompleted = true;
                }
                rockContext.SaveChanges();

                #endregion Mark Completed Occurrences

                #region Cleanup

                var reservationLocationService = new ReservationLocationService(rockContext);
                var reservationLocationIds     = reservationLocationService.Queryable().AsNoTracking().Select(rl => rl.Id);

                // Delete any orphaned RoomOccurrences ( tied to invalid/deleted ReservationId )
                zrOccurrenceService = new RoomOccurrenceService(rockContext);
                var orphanedOccs = zrOccurrenceService.Queryable()
                                   .Where(ro => ro.EntityTypeId == reservationLocationEntityTypeId &&
                                          !reservationLocationIds.Any(id => id == ro.EntityId));
                if (orphanedOccs.Count() > 0)
                {
                    if (verboseLogging)
                    {
                        LogEvent(rockContext, "Zoom Room Reservation Sync", string.Format("Preparing to delete {0} orphaned RoomOccurrence(s)."));
                    }
                    zrOccurrenceService.DeleteRange(orphanedOccs);
                    var errors = new List <string>();
                    LogEvent(null, "Zoom Room Reservation Sync", string.Format("{0} orphaned RoomOccurrence(s) deleted.", orphanedOccs.Count()));
                    if (verboseLogging)
                    {
                        LogEvent(null, "Zoom Room Reservation Sync", "Deleting related Zoom Meetings.");
                    }
                    DeleteOccurrenceZoomMeetings(orphanedOccs, zoom);
                    rockContext.SaveChanges();
                }

                // Delete any active Room Occurrences tied to Zoom Meetings that no longer exist.
                var linkedOccurrences = zrOccurrenceService
                                        .Queryable()
                                        .AsNoTracking()
                                        .Where(ro => ro.EntityTypeId == reservationLocationEntityTypeId &&
                                               ro.ZoomMeetingId > 0 &&
                                               !ro.IsCompleted &&
                                               ro.StartTime >= beginDateTime);
                var zoomMeetings = new List <Meeting>();
                foreach (var zrl in linkedZoomRoomLocations)
                {
                    zoomMeetings.AddRange(zoom.GetZoomMeetings(zrl.Value, MeetingListType.Upcoming));
                }
                var zoomMeetingIds      = zoomMeetings.Select(m => m.Id).ToList();
                var orphanedOccurrences = linkedOccurrences.Where(ro => !zoomMeetingIds.Any(mid => mid == ro.ZoomMeetingId));
                if (orphanedOccurrences.Count() > 0)
                {
                    zrOccurrenceService.DeleteRange(orphanedOccurrences);
                    rockContext.SaveChanges();
                }

                // Attempt to create Zoom Room Meeting for any Room Occurrences that may have had previous issues.
                var unlinkedOccurrences = zrOccurrenceService
                                          .Queryable()
                                          .Where(ro => ro.EntityTypeId == reservationLocationEntityTypeId &&
                                                 (!ro.ZoomMeetingId.HasValue || ro.ZoomMeetingId <= 0) &&
                                                 !ro.IsCompleted &&
                                                 ro.StartTime >= beginDateTime &&
                                                 (ro.ZoomMeetingRequestStatus == ZoomMeetingRequestStatus.Failed || ro.ZoomMeetingRequestStatus == ZoomMeetingRequestStatus.ZoomRoomOffline));

                foreach (var rOcc in unlinkedOccurrences)
                {
                    var rLoc = reservationLocationService.Queryable("Location").FirstOrDefault(rl => rl.Id == rOcc.EntityId);
                    rLoc.Location.LoadAttributes();
                    rOcc.ZoomMeetingRequestStatus = ZoomMeetingRequestStatus.Requested;
                    var zoomRoomDV = DefinedValueCache.Get(rLoc.Location.GetAttributeValue("rocks.kfs.ZoomRoom").AsGuid());
                    CreateOccurrenceZoomMeeting(rOcc, zoomRoomDV, zoom);
                }
                if (unlinkedOccurrences.Count() > 0)
                {
                    rockContext.SaveChanges();
                }

                #endregion Cleanup

                #region External Zoom Meetings

                var scheduleService           = new ScheduleService(rockContext);
                var reservationService        = new ReservationService(rockContext);
                var reservationTypeService    = new ReservationTypeService(rockContext);
                var zoomImportReservationType = reservationTypeService.Get(RoomReservationType.ZOOMROOMIMPORT.AsGuid());

                // Create RoomOccurrences for any Zoom Room meetings created outside of Rock
                if (importMeetings && linkedZoomRoomLocations.Count > 0)
                {
                    var linkedMeetings   = linkedOccurrences.Select(ro => ro.ZoomMeetingId).ToList();
                    var zoomRoomMeetings = zoomMeetings.Where(m => m.Start_Time > beginDateTime);
                    var missingMeetings  = zoomRoomMeetings.Where(m => !linkedMeetings.Any(mid => mid == m.Id));
                    if (missingMeetings.Count() > 0)
                    {
                        foreach (var zrl in linkedZoomRoomLocations)
                        {
                            foreach (var meeting in missingMeetings.Where(m => m.Host_Id == zrl.Value))
                            {
                                // Build the iCal string as it is a required property on the Schedule for Room Reservation block to display the Reservation
                                var meetingLocalTime = meeting.Start_Time.UtcDateTime.ToLocalTime();
                                var calendarEvent    = new Event
                                {
                                    DtStart = new CalDateTime(meetingLocalTime),
                                    DtEnd   = new CalDateTime(meetingLocalTime.AddMinutes(meeting.Duration)),
                                    DtStamp = new CalDateTime(meetingLocalTime.Year, meetingLocalTime.Month, meetingLocalTime.Day)
                                };
                                var calendar = new Calendar();
                                calendar.Events.Add(calendarEvent);
                                var serializer = new CalendarSerializer(calendar);

                                var schedule = new Schedule
                                {
                                    Guid = Guid.NewGuid(),
                                    EffectiveStartDate = meetingLocalTime,
                                    EffectiveEndDate   = meetingLocalTime.AddMinutes(meeting.Duration),
                                    IsActive           = true,
                                    iCalendarContent   = serializer.SerializeToString()
                                };
                                scheduleService.Add(schedule);

                                var location       = locationService.Get(zrl.Key);
                                var newReservation = new Reservation
                                {
                                    Name = location.Name.Left(50),    // NOTE: Reservation.Name is limited to 50 chars but Location.Name is up to 100 chars.
                                    ReservationTypeId = zoomImportReservationType.Id,
                                    Guid            = Guid.NewGuid(),
                                    Schedule        = schedule,
                                    NumberAttending = 0,
                                    Note            = string.Format("Created from import of \"{0}\" meeting ({1}) from Zoom Room \"{2}\".", meeting.Topic, meeting.Id, zrl.Value),
                                    ApprovalState   = ReservationApprovalState.Approved
                                };
                                reservationService.Add(newReservation);

                                var reservationLocation = new ReservationLocation
                                {
                                    Reservation   = newReservation,
                                    Location      = location,
                                    ApprovalState = ReservationLocationApprovalState.Approved
                                };
                                reservationLocationService.Add(reservationLocation);
                                rockContext.SaveChanges();

                                var occurrence = new RoomOccurrence
                                {
                                    ZoomMeetingId = meeting.Id,
                                    EntityTypeId  = reservationLocationEntityTypeId,
                                    EntityId      = reservationLocation.Id,
                                    Schedule      = schedule,
                                    LocationId    = reservationLocation.LocationId,
                                    Topic         = meeting.Topic,
                                    StartTime     = meetingLocalTime,
                                    Password      = meeting.Password,
                                    Duration      = meeting.Duration,
                                    TimeZone      = meeting.Timezone
                                };
                                zrOccurrenceService.Add(occurrence);
                                rockContext.SaveChanges();
                            }
                        }
                    }
                }

                #endregion External Zoom Meetings

                #region Process Reservations

                var reservations = reservationService.Queryable("Schedule,ReservationLocations,ReservationType")
                                   .AsNoTracking()
                                   .Where(r => r.ModifiedDateTime >= beginDateTime &&
                                          r.ReservationTypeId != zoomImportReservationType.Id &&
                                          (r.ApprovalState == ReservationApprovalState.Approved ||
                                           (!r.ReservationType.IsReservationBookedOnApproval && r.ApprovalState != ReservationApprovalState.Cancelled && r.ApprovalState != ReservationApprovalState.Denied && r.ApprovalState != ReservationApprovalState.Draft)) &&
                                          r.ReservationLocations.Any(rl => zrLocationIds.Contains(rl.LocationId)) &&
                                          r.Schedule != null &&
                                          ((r.Schedule.EffectiveEndDate != null && r.Schedule.EffectiveEndDate > DbFunctions.AddDays(RockDateTime.Today, -1)) ||
                                           (r.Schedule.EffectiveEndDate == null && r.Schedule.EffectiveStartDate != null && r.Schedule.EffectiveStartDate > DbFunctions.AddDays(RockDateTime.Today, -1))))
                                   .ToList();
                var resLocationIdsToProcess = new List <int>();
                var zrOccurrencesAdded      = 0;
                if (verboseLogging)
                {
                    LogEvent(rockContext, "Zoom Room Reservation Sync", string.Format("{0} Room Reservation(s) to be processed", reservations.Count()));
                }
                foreach (var res in reservations)
                {
                    foreach (var rl in res.ReservationLocations.Where(rl => zrLocationIds.Contains(rl.LocationId)).ToList())
                    {
                        rl.Location.LoadAttributes();
                        var zoomRoomDV = DefinedValueCache.Get(rl.Location.AttributeValues.FirstOrDefault(v => v.Key == "rocks.kfs.ZoomRoom").Value.Value.AsGuid());
                        var zrPassword = zoomRoomDV.GetAttributeValue("rocks.kfs.ZoomMeetingPassword");
                        resLocationIdsToProcess.Add(rl.Id);
                        var resLocOccurrences = zrOccurrenceService.Queryable().Where(ro => ro.EntityTypeId == reservationLocationEntityTypeId && ro.EntityId == rl.Id);

                        // One-Time Schedule
                        if (res.Schedule.EffectiveEndDate is null || res.Schedule.EffectiveStartDate.Value == res.Schedule.EffectiveEndDate.Value)
                        {
                            var occurrence = new RoomOccurrence();
                            if (resLocOccurrences.Count() == 0)
                            {
                                occurrence = new RoomOccurrence
                                {
                                    Id                       = 0,
                                    EntityTypeId             = reservationLocationEntityTypeId,
                                    EntityId                 = rl.Id,
                                    ScheduleId               = res.ScheduleId,
                                    LocationId               = rl.LocationId,
                                    Topic                    = res.Name,
                                    StartTime                = res.Schedule.FirstStartDateTime.Value,
                                    Password                 = zrPassword,
                                    Duration                 = res.Schedule.DurationInMinutes,
                                    IsCompleted              = false,
                                    ZoomMeetingRequestStatus = ZoomMeetingRequestStatus.Requested
                                };
                                zrOccurrenceService.Add(occurrence);
                                rockContext.SaveChanges();
                                zrOccurrencesAdded++;

                                if (CreateOccurrenceZoomMeeting(occurrence, zoomRoomDV, zoom))
                                {
                                    rockContext.SaveChanges();
                                }
                            }
                            else
                            {
                                Meeting connectedMeeting = null;
                                var     updateMeeting    = false;
                                occurrence = resLocOccurrences.FirstOrDefault();
                                if (occurrence.ZoomMeetingId.HasValue && occurrence.ZoomMeetingId.Value > 0)
                                {
                                    connectedMeeting = zoomMeetings.FirstOrDefault(m => m.Id == occurrence.ZoomMeetingId.Value);
                                    if (connectedMeeting == null)
                                    {
                                        occurrence.ZoomMeetingId = null;
                                    }
                                }
                                if (occurrence.IsCompleted)
                                {
                                    occurrence.IsCompleted = false;
                                }
                                if (occurrence.ScheduleId != res.ScheduleId)
                                {
                                    occurrence.ScheduleId = res.ScheduleId;
                                }
                                if (occurrence.StartTime != res.Schedule.FirstStartDateTime.Value)
                                {
                                    occurrence.StartTime = res.Schedule.FirstStartDateTime.Value;
                                }
                                if (connectedMeeting != null && connectedMeeting.Start_Time != occurrence.StartTime.ToRockDateTimeOffset())
                                {
                                    connectedMeeting.Start_Time = occurrence.StartTime.ToRockDateTimeOffset();
                                    updateMeeting = true;
                                }
                                if (occurrence.Duration != res.Schedule.DurationInMinutes)
                                {
                                    occurrence.Duration = res.Schedule.DurationInMinutes;
                                    if (connectedMeeting != null)
                                    {
                                        connectedMeeting.Duration = res.Schedule.DurationInMinutes;
                                        updateMeeting             = true;
                                    }
                                }
                                if (occurrence.Topic != res.Name)
                                {
                                    occurrence.Topic = res.Name;
                                    if (connectedMeeting != null)
                                    {
                                        connectedMeeting.Topic = res.Name;
                                        updateMeeting          = true;
                                    }
                                }
                                if (updateMeeting)
                                {
                                    zoom.UpdateMeeting(connectedMeeting);
                                }
                            }
                        }