using System.Text; using FsToolbox.Cli.Helper; using FsToolbox.Cli.Tasks; using NLog; namespace FsToolbox.Cli { public partial class CustomTasks { private static readonly Logger Logger = LoggingService.GetLogger(); #region Public Method CheckAldiMembershipsAsync /// /// Analyzes ALDI store memberships in the Siegen region and writes users with multiple memberships to a report file. /// /// The HTTP client used to perform the requests. public static async Task CheckAldiMembershipsAsync(HttpClient httpClient) { await AuthHelper.EnsureAuthenticationAsync(httpClient); // Implementation for checking Aldi memberships would go here. var stores = await RegionTasks.GetStoresInRegionAsync(httpClient, 139); var activeAldis = stores.Where(x => x.CooperationStatus == RegionTasks.CooperationStatus.Cooperating && x.Name.Contains("ALDI", StringComparison.CurrentCultureIgnoreCase)).ToList(); Logger.Info("Found {ActiveCount} active ALDI stores in region Siegen.", activeAldis.Count); var aldiMembers = new List(); // Collect members from each active ALDI store foreach (var activeAldi in activeAldis) { Thread.Sleep(200); Logger.Info("Checking members for store: {Store}", activeAldi.Name); var members = await StoreTasks.GetStoreMembersAsync(httpClient, activeAldi.Id); var activeMembers = members.Where(x => x is { Verified: StoreTasks.VerifiedStatus.Verified, Team_Active: StoreTasks.TeamActiveStatus.Active }).ToList(); activeMembers.ForEach(x => aldiMembers.Add(new(activeAldi, x))); } // Group memberships var grouped = aldiMembers.GroupBy(x => x.Member.Id); // Find groups with more than two memberships var multipleMemberships = grouped.Where(g => g.Count() > 2).OrderByDescending(g => g.Count()).ToList(); Logger.Info("Users with more than two ALDI store memberships: {Count}", multipleMemberships.Count); Logger.Info("Saving data to file."); var sb = new StringBuilder(); sb.AppendLine("ALDI Store Memberships Report"); sb.AppendLine("Generated: " + DateTime.Now.ToString("dd.MM.yyyy HH:mm")); sb.AppendLine("============================"); sb.AppendLine(); foreach (var group in multipleMemberships) { sb.AppendLine($"{group.First().Member.Name} ({group.Key}) - {group.Count()} ALDIs"); foreach (var membership in group) sb.AppendLine($" - {membership.Store.Name}"); sb.AppendLine(); } // Write to file with timestamp var timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss"); var filename = $"AldiMemberships_{timestamp}.txt"; await File.WriteAllTextAsync(filename, sb.ToString()); } #endregion #region Public Method AnalyzeSlotUnregistrationsAsync /// /// Retrieves the store log for the first cooperating store in region 135 for the last month. /// /// The HTTP client used to perform the requests. public static async Task AnalyzeSlotUnregistrationsAsync(HttpClient httpClient) { await AuthHelper.EnsureAuthenticationAsync(httpClient); var stores = await RegionTasks.GetStoresInRegionAsync(httpClient, 139); var cooperatingStores = stores.Where(x => x.CooperationStatus == RegionTasks.CooperationStatus.Cooperating).ToList(); if (cooperatingStores.Count == 0) { Logger.Info("No cooperating stores found in region 139."); return; } var fromDate = DateTime.Today.AddMonths(-1).ToString("yyyy-MM-dd"); var toDate = DateTime.Today.ToString("yyyy-MM-dd"); const string storeLogActionIds = "12"; var collected = new List(); foreach (var store in cooperatingStores) { Logger.Info("Fetching store log for {StoreId} {StoreName} ({From} to {To})", store.Id, store.Name, fromDate, toDate); var logEntries = await StoreTasks.GetStoreLogEntriesAsync(httpClient, store.Id, fromDate, toDate, storeLogActionIds); foreach (var entry in logEntries) { if (entry.ActingFoodsaver == null) continue; collected.Add(new StoreLogProfileEntry(entry.ActingFoodsaver, store, entry.DateReference, entry.PerformedAt)); } Logger.Info("Collected {Count} log entries for {StoreName}.", collected.Count, store.Name); Thread.Sleep(1000); } // Group entries by profile and sort by count var grouped = collected .GroupBy(x => x.Profile.Id) .Select(g => new { Profile = g.First().Profile, Count = g.Count() }) .OrderByDescending(x => x.Count) .ToList(); // Create txt file report var sb = new StringBuilder(); sb.AppendLine("Store Log Unregistration Report"); sb.AppendLine("Generated: " + DateTime.Now.ToString("dd.MM.yyyy HH:mm")); sb.AppendLine($"Timeframe: {fromDate} to {toDate}"); sb.AppendLine($"Total analyzed stores: {cooperatingStores.Count} (cooperating)"); sb.AppendLine($"Total unregistrations: {collected.Count}"); sb.AppendLine("Region: Siegen (139)"); sb.AppendLine("============================"); sb.AppendLine(); foreach (var entry in grouped) { sb.AppendLine($"{entry.Profile.Name} ({entry.Profile.Id}) - {entry.Count} unregistrations"); sb.AppendLine("----------------------------"); // List the stores and dates var userEntries = collected .Where(x => x.Profile.Id == entry.Profile.Id) .OrderBy(x => x.DateReference) .ToList(); foreach (var userEntry in userEntries) sb.AppendLine($" - {userEntry.Store.Name} on {userEntry.DateReference} (Performed at: {userEntry.PerformedAt})"); sb.AppendLine(); } // Write to file with timestamp var timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss"); var filename = $"SlotUnregistrations_{timestamp}.txt"; await File.WriteAllTextAsync(filename, sb.ToString()); } #endregion #region Public Method AnalyzeEarlySlotRegistrationsAsync /// /// Identifies store log entries where a user registered earlier than the automatic slot creation interval. /// /// The HTTP client used to perform the requests. public static async Task AnalyzeEarlySlotRegistrationsAsync(HttpClient httpClient) { await AuthHelper.EnsureAuthenticationAsync(httpClient); const int regionId = 139; var stores = await RegionTasks.GetStoresInRegionAsync(httpClient, regionId); var cooperatingStores = stores.Where(x => x.CooperationStatus == RegionTasks.CooperationStatus.Cooperating).ToList(); if (cooperatingStores.Count == 0) { Logger.Info("No cooperating stores found in region {RegionId}.", regionId); return; } var fromDate = DateTime.Today.AddMonths(-6).ToString("yyyy-MM-dd"); var toDate = DateTime.Today.ToString("yyyy-MM-dd"); const string storeLogActionIds = "11"; var ignoredStoreIds = new HashSet { 51224, 61913 }; var storesToProcess = cooperatingStores .Where(x => !ignoredStoreIds.Contains(x.Id)) .ToList(); Logger.Info("Debug mode: processing first {Count} cooperating stores.", storesToProcess.Count); var analyzed = new List(); foreach (var store in storesToProcess) { var info = await StoreTasks.GetStoreInformationAsync(httpClient, store.Id); if (info == null) { Logger.Info("Store information not available for {StoreId}.", store.Id); continue; } var calendarInterval = info.CalendarInterval; if (calendarInterval <= 0) { Logger.Info("Skipping {StoreName} because calendar interval is {Interval}.", store.Name, calendarInterval); continue; } Logger.Info("Calendar interval for {StoreName} is {Interval} seconds.", store.Name, calendarInterval); var logEntries = await StoreTasks.GetStoreLogEntriesAsync(httpClient, store.Id, fromDate, toDate, storeLogActionIds); Thread.Sleep(1000); foreach (var entry in logEntries) { if (entry.DateReference == null) continue; var secondsDifference = (entry.DateReference.Value - entry.PerformedAt).TotalSeconds; if (secondsDifference <= calendarInterval) continue; analyzed.Add(new StoreLogIntervalEntry(store, entry, secondsDifference, calendarInterval, true)); } } var exceeding = analyzed.Count; var sb = new StringBuilder(); sb.AppendLine("Early Slot Registration Report"); sb.AppendLine("Generated: " + DateTime.Now.ToString("dd.MM.yyyy HH:mm")); sb.AppendLine($"Region: {regionId}"); sb.AppendLine($"Stores analyzed: {storesToProcess.Count}"); sb.AppendLine($"Timeframe: {fromDate} to {toDate}"); sb.AppendLine($"Total log entries: {analyzed.Count}"); sb.AppendLine($"Exceeding interval: {exceeding}"); sb.AppendLine("============================"); sb.AppendLine(); foreach (var entry in analyzed.OrderByDescending(x => x.SecondsDifference)) { var foodsaver = entry.Entry.ActingFoodsaver; var foodsaverName = foodsaver?.Name ?? "(unknown)"; var foodsaverId = foodsaver?.Id.ToString() ?? "n/a"; var reference = entry.Entry.DateReference?.ToString("dd.MM.yyyy - HH:mm") ?? "n/a"; var performed = entry.Entry.PerformedAt.ToString("dd.MM.yyyy - HH:mm"); var intervalWeeks = entry.CalendarIntervalSeconds / (60.0 * 60 * 24 * 7); var earlySeconds = entry.SecondsDifference - entry.CalendarIntervalSeconds; var earlySpan = TimeSpan.FromSeconds(earlySeconds); sb.AppendLine($"- {foodsaverName} ({foodsaverId}) | Store: {entry.Store.Name} ({entry.Store.Id}) | Performed: {performed} | Slot Date: {reference} | Interval: {intervalWeeks:F2} weeks | Early: {earlySpan.Days}d {earlySpan.Hours}h {earlySpan.Minutes}m"); } var timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss"); var filename = $"EarlySlotRegistrations_{timestamp}.txt"; await File.WriteAllTextAsync(filename, sb.ToString()); Logger.Info("Report saved: {FileName}", filename); } #endregion #region Public Method ConfirmUnconfirmedPickupsLindenbergAsync /// /// Confirms all unconfirmed pickup slots for the configured Lindenberg store after user confirmation. /// /// The HTTP client used to perform the requests. public static async Task ConfirmUnconfirmedPickupsLindenbergAsync(HttpClient httpClient) { await AuthHelper.EnsureAuthenticationAsync(httpClient); var toConfirm = new List<(string Name, string Date, int User)>(); // Collect unconfirmed slots var pickups = await StoreTasks.GetPickupsAsync(httpClient, 56749); foreach (var pickup in pickups) foreach (var slot in pickup.OccupiedSlots) if (!slot.IsConfirmed) { var pickupDate = pickup.Date; var userId = slot.Profile.Id; var userName = slot.Profile.Name; toConfirm.Add((userName, pickupDate, userId)); } if (toConfirm.Count != 0) { toConfirm.ForEach(x => Logger.Info("Slot found: {Name} on {Date}", x.Name, x.Date)); } else { Logger.Info("No unconfirmed slots found."); return; } // Confirm question Logger.Info("Confirm all unconfirmed slots? (y/n) (Enter for default): "); var input = Console.ReadLine(); if (string.IsNullOrEmpty(input) || input.Equals("y", StringComparison.OrdinalIgnoreCase)) { Logger.Info("Confirming unconfirmed slots..."); // Confirm the unconfirmed slots foreach (var confirmEntry in toConfirm) await StoreTasks.PatchPickupAsync(httpClient, 52170, confirmEntry.Date, confirmEntry.User); Logger.Info("Done confirming slots."); } } #endregion } }