private async Task <TimesheetBasicData[]> GetTimesheetBasicDataAsync(TimesheetStates state, DateTime date, DateTime startOfWeek) { var timesheets = new List <OpenAirClient.Timesheet>(); if (state == TimesheetStates.Unsubmitted) { var nextDay = date.AddDays(1); timesheets.AddRange(await _client.GetTimesheetsByStatusAsync(startOfWeek, nextDay, "S")); timesheets.AddRange(await _client.GetTimesheetsByStatusAsync(startOfWeek, nextDay, "A")); } else if (state == TimesheetStates.Unapproved) { var lastWeek = startOfWeek.AddDays(-DaysInWeek); timesheets.AddRange(await _client.GetTimesheetsByStatusAsync(lastWeek, startOfWeek, "A")); } var timesheetsData = timesheets .GroupBy(it => it.UserId) .Select(it => new TimesheetBasicData( it.Key.Value, it.Where(sheet => sheet.StartDate.Date > startOfWeek) .Where(sheet => (state == TimesheetStates.Unapproved && sheet.Status == "A") || (state == TimesheetStates.Unsubmitted && (sheet.Status == "S" || sheet.Status == "A"))) .Sum(sheet => sheet.Total ?? 0))) .ToArray(); return(timesheetsData); }
private async Task SendNotificationsAsync( Dictionary <string, IReadOnlyList <Timesheet> > timesheets, string key, string email, bool notify, TimesheetStates state, DateTime scheduleDate, GoogleChatAddress address, IReadOnlyList <string> customerToExcludes, ITimesheetProcessor processor) { if (!timesheets.TryGetValue(key, out var timesheetValues)) { timesheetValues = await processor.GetTimesheetsAsync(scheduleDate, state, email, true, customerToExcludes); timesheets.Add(key, timesheetValues); } await processor.SendTimesheetNotificationsToUsersAsync( timesheetValues, email, department : null, notify : notify, notifyByEmail : false, state, address, _hangoutsChatConnector); }
/// <summary>Get timesheets and notifies by message or email the users asynchronous.</summary> public async Task NotifyAsync( DateTime date, TimesheetStates state, string email, IReadOnlyList <string> customersToExclude, string department, bool notify, bool notifyByEmail, bool filterOutSender, GoogleChatAddress address, IHangoutsChatConnector connector) => await SendTimesheetNotificationsToUsersAsync( await _openAirConnector.GetUnsubmittedTimesheetsAsync( date, Contract.LocalDateTime.Date, state, email, filterOutSender, TimesheetsProperties.UserMaxHours, customersToExclude), email, department, notify, notifyByEmail, state, address, connector);
/// <inheritdoc/> public Task <IReadOnlyList <Timesheet> > GetTimesheetsAsync( DateTime dateTime, TimesheetStates state, string senderEmail, bool filterOutSender, IReadOnlyList <string> customersToExclude) => _openAirConnector.GetUnsubmittedTimesheetsAsync( dateTime, Contract.LocalDateTime.Date, state, senderEmail, filterOutSender, TimesheetsProperties.UserMaxHours, customersToExclude);
private async Task SaveGlobalStatisticsAsync( DateTime scheduleDate, IReadOnlyList <string> customerToExcludes, IPluginPropertiesAccessor accessor, ITimesheetProcessor processor) { var group = accessor.GetPluginPropertyGroup(TimesheetsProperties.GlobalStatisticsGroup).FirstOrDefault(); if (group == null) { return; } var cron = group.GetValue <string>(TimesheetsProperties.GlobalStatisticsCron); if (string.IsNullOrEmpty(cron) || !CronCheck(cron, scheduleDate)) { return; } const TimesheetStates state = TimesheetStates.Unsubmitted; var dateValue = scheduleDate.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture); var timeValue = scheduleDate.ToString("HH:00", CultureInfo.InvariantCulture); var email = group.GetValue <string>(TimesheetsProperties.GlobalStatisticsEmail); var timesheets = await processor.GetTimesheetsAsync(scheduleDate, state, email, true, customerToExcludes); var statistics = new Statistics <TimesheetStatistics[]> { Id = Guid.NewGuid().ToString(), Date = dateValue, Time = timeValue, Type = nameof(TimesheetStatistics), Data = timesheets .Select(it => new TimesheetStatistics { UserName = it.UserName, ManagerName = it.ManagerName, DepartmentName = it.DepartmentName, State = state, }) .ToArray() }; await _storageService.AddOrUpdateStatisticsAsync(statistics); }
/// <summary>Get timesheets and notifies by message or email the users asynchronous.</summary> public Task NotifyAsync( DateTime date, TimesheetStates state, string email, string[] customersToExclude, string department, bool notify, bool notifyByEmail, GoogleChatAddress address, IHangoutsChatConnector connector) => _openAirConnector.GetUnsubmittedTimesheetsAsync(date, state, email, customersToExclude) .ContinueWith(task => ProcessNotifyAsync( task.Result, email, department, notify, notifyByEmail, state, address, connector));
/// <inheritdoc/> public async Task <IReadOnlyList <Timesheet> > GetUnsubmittedTimesheetsAsync(DateTime date, TimesheetStates state, string senderEmail, string[] filterByCustomers) { var requiredHours = date.DayOfWeek == DayOfWeek.Saturday ? 40 : (int)date.DayOfWeek * 8; var toweek = date.AddDays(-(double)date.DayOfWeek); var lastWeek = toweek.AddDays(-7); var timesheets = new List <OpenAirClient.Timesheet>(); var normalizedCustomerNames = filterByCustomers?.Select(NormalizeValue).ToArray(); timesheets.AddRange(await _client.GetTimesheetsAsync(lastWeek, lastWeek.AddDays(2))); timesheets.AddRange(await _client.GetTimesheetsAsync(toweek, date.AddDays(1))); var timesheetsData = timesheets .GroupBy(it => it.UserId) .Select(it => new TimesheetBasicData( it.Key.Value, it.FirstOrDefault()?.Name, it.Where(sheet => sheet.StartDate.Date > toweek) .Where(sheet => (state == TimesheetStates.Unapproved && sheet.Status == "A") || (state == TimesheetStates.Unsubmitted && (sheet.Status == "S" || sheet.Status == "A"))) .Sum(sheet => sheet.Total ?? 0))) .ToArray(); var users = await _storageService.GetAllActiveUsersAsync(); // 0. Filter out the sender. // 1. Filter out only users where the sender is line manager. // 2. Filter out customers. // 3. Select timesheet var result = users .Where(it => it.Email != senderEmail) .Where(it => IsManager(it, senderEmail, users, new List <string>())) .Where(it => FiterCustomersByNames(it.Customers, normalizedCustomerNames)) .Select(user => new TimesheetExtendedData(timesheetsData.FirstOrDefault(it => it.UserId == user.OpenAirUserId), user)) .Where(it => it.Timesheet.Total < requiredHours) .Select(it => new Timesheet { Name = it.Timesheet.Name, Total = it.Timesheet.Total, UserName = FormatDisplayName(it.User.Name), UserEmail = it.User.Email, DepartmentName = it.User.Department.Name, ManagerName = FormatDisplayName(FindUser(it.User.Manager, users)?.Name) }) .ToArray(); return(result); }
/// <summary>Gets the open air text.</summary> public static string GetText(TimesheetStates state, OpenAirTextTypes type) => Phrases[type.ToString() + state];
/// <inheritdoc/> public async Task <IReadOnlyList <Timesheet> > GetUnsubmittedTimesheetsAsync( DateTime date, DateTime today, TimesheetStates state, string senderEmail, bool filterSender, string userPropertyMaxHoursKey, IReadOnlyList <string> filterByCustomers) { var startOfWeek = date.AddDays(-(double)date.DayOfWeek); var timesheetsData = await GetTimesheetBasicDataAsync(state, date, startOfWeek); var users = await _storageService.GetAllActiveUsersAsync(); var normalizedCustomerNames = filterByCustomers?.Select(NormalizeValue).ToArray(); var isEndOfMonthReport = date.Date == today && today.AddDays(1).Day == 1; var isTodayWeekend = today.DayOfWeek == DayOfWeek.Saturday || today.DayOfWeek == DayOfWeek.Sunday; var dayOfWeekMultiplier = isTodayWeekend ? WorkDaysInWeek : (int)today.DayOfWeek; // 1. Filter out user that are not started. // 2. Filter out users by a predefined list of emails // 3. Filter out only users where the sender is line manager. // 4. Filter out customers. // 5. Select timesheet var result = users .Where(it => !it.StartDate.HasValue || it.StartDate <= today) .Where(it => !filterSender || !senderEmail.Equals(it.Email, StringComparison.InvariantCultureIgnoreCase)) .Where(it => it.Manager != null) .Where(it => { try { return((it.Department?.Owner?.Email.Equals(senderEmail, StringComparison.InvariantCultureIgnoreCase) ?? false) || users.IsRequestorManager(it, senderEmail)); } catch (Exception ex) { Debug.Write(ex.Message); return(false); } }) .Where(it => { try { return(FilterCustomersByNames(it.Customers, normalizedCustomerNames)); } catch (Exception ex) { Debug.Write(ex.Message); return(false); } }) .Select(user => { try { // Normally how many hours this users should work in week? var requiredUserHoursPerWeek = user.Properties .GetAllUserValues <int>(userPropertyMaxHoursKey) .DefaultIfEmpty(WorkingHoursInWeek) .First(); var requiredDays = WorkDaysInWeek; // If user starts that week, calculate valid dates. if (user.StartDate.HasValue && user.StartDate.Value >= startOfWeek && user.StartDate.Value <= date) { // If date difference + 1. So if we start today (date (today) - startDate (today)) + 1 = 1. requiredDays = (date.Date - user.StartDate.Value).Days + 1; } else if (isEndOfMonthReport) { requiredDays = dayOfWeekMultiplier; } return(new TimesheetExtendedData( timesheetsData.FirstOrDefault(it => it.UserId == user.OpenAirUserId), user, requiredHours: (requiredUserHoursPerWeek / WorkDaysInWeek) * requiredDays)); } catch (Exception ex) { Debug.Write(ex.Message); return(new TimesheetExtendedData(null, user, WorkingHoursInWeek)); } }) .Where(it => it.Timesheet.Total < it.RequiredHours) .Select(it => new Timesheet { Total = it.Timesheet.Total, UtilizationInHours = it.RequiredHours, UserName = FormatDisplayName(it.User?.Name ?? "Unknown User"), UserEmail = it.User.Email, DepartmentName = it.User.Department.Name, ManagerName = FormatDisplayName(users.FindUserByRef(it.User.Manager)?.Name) }) .ToArray(); return(result); }
private async Task <IReadOnlyList <string> > NotifyUsersOverChatAsync( IHangoutsChatConnector connector, TimesheetStates state, Timesheet[] timesheets) { var notifiedUserList = new List <string>(); var addressesForUpdate = new List <GoogleAddress>(); var storeAddresses = await _storageService.GetAddressesAsync(); var emails = timesheets.Select(it => it.UserEmail).ToArray(); var filteredAddresses = storeAddresses.Where(it => emails.Contains(it.UserEmail)).ToArray(); IReadOnlyList <GoogleAddress> privateAddresses = new GoogleAddress[0]; if (filteredAddresses.Length < timesheets.Length) { var storeAddressesNames = storeAddresses.Select(it => it.SpaceName).Distinct().ToArray(); privateAddresses = connector .GetPrivateAddress(storeAddressesNames) .Select(it => new GoogleAddress { SpaceName = it.Space.Name, UserName = it.Sender.Name, UserDisplayName = it.Sender.DisplayName }) .ToArray(); // Store inactive spaces, so not to be requested the next time addressesForUpdate.AddRange(privateAddresses.Where(it => it.UserName == null)); } var textMessage = OpenAirText.GetText(state, OpenAirTextTypes.Notify); foreach (var timesheet in timesheets) { var message = timesheet.UserName + textMessage; var addr = filteredAddresses.FirstOrDefault(it => it.UserEmail == timesheet.UserEmail); if (addr == null) { addr = privateAddresses.FirstOrDefault(it => it.UserDisplayName == timesheet.UserName); if (addr == null) { continue; } addr.UserEmail = timesheet.UserEmail; addressesForUpdate.Add(addr); } notifiedUserList.Add(timesheet.UserName); await connector.SendMessageAsync( message, new GoogleChatAddress(addr.SpaceName, string.Empty, "DM", addr.UserName, addr.UserDisplayName)); } if (addressesForUpdate.Count > 0) { await _storageService.AddAddressesAsync(addressesForUpdate); } return(notifiedUserList); }
/// <summary>Processes the specified timesheets.</summary> public async Task SendTimesheetNotificationsToUsersAsync( IReadOnlyList <Timesheet> timesheets, string email, string department, bool notify, bool notifyByEmail, TimesheetStates state, GoogleChatAddress address, IHangoutsChatConnector connector) { string text; var filteredTimesheet = timesheets .Where(it => department == null || department.Equals(it.DepartmentName, StringComparison.InvariantCultureIgnoreCase)) .OrderBy(it => it.ManagerName) .ThenBy(it => it.UserName) .ToArray(); var notifiedUserList = new List <string>(); if (filteredTimesheet.Length == 0) { var responses = OpenAirText.GetText(state, OpenAirTextTypes.AllAreDone).Split('|', StringSplitOptions.RemoveEmptyEntries); var rand = new Random(); text = responses[rand.Next(0, responses.Length - 1)]; } else if (notify && filteredTimesheet.Length > 0) { notifiedUserList.AddRange( await NotifyUsersOverChatAsync(connector, state, filteredTimesheet)); if (notifyByEmail) { var textMessage = OpenAirText.GetText(state, OpenAirTextTypes.Notify); var emails = filteredTimesheet .Where(it => !notifiedUserList.Contains(it.UserName)) .Apply(it => notifiedUserList.Add(it.UserName)) .Select(it => it.UserEmail) .Distinct() .ToArray(); await _mailService.SendMailAsync("Timesheet is pending", textMessage, emails); } text = notifiedUserList.Count == filteredTimesheet.Length ? string.Format( CultureInfo.InvariantCulture, OpenAirText.GetText(state, OpenAirTextTypes.AllAreNotified), notifiedUserList.Count) : OpenAirText.GetText(state, OpenAirTextTypes.SomeAreNotified) + GetCardText(filteredTimesheet, notifiedUserList); } else { text = OpenAirText.GetText(state, OpenAirTextTypes.SomeAreDone) + GetCardText(filteredTimesheet, notifiedUserList); } var paragraph = new TextParagraph { Text = text }; var card = ChatEventFactory.CreateCard(paragraph); if (address != null) { await connector.SendMessageAsync(null, address, card); } else if (email != null) { var emailedUsers = string.Join("</b><br/><b>", notifiedUserList); var emailText = $"{text}<br/><br/><b>The following people where notified by a direct massage or email:" + $"<br/><b>{emailedUsers}</b>"; await _mailService.SendMailAsync("Users not notified", emailText, email); } }