313 lines
14 KiB
C#
313 lines
14 KiB
C#
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<CustomTasks>();
|
|
|
|
#region Public Method CheckAldiMembershipsAsync
|
|
|
|
/// <summary>
|
|
/// Analyzes ALDI store memberships in the Siegen region and writes users with multiple memberships to a report file.
|
|
/// </summary>
|
|
/// <param name="httpClient">The HTTP client used to perform the requests.</param>
|
|
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<AldiMember>();
|
|
|
|
// 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
|
|
|
|
/// <summary>
|
|
/// Retrieves the store log for the first cooperating store in region 135 for the last month.
|
|
/// </summary>
|
|
/// <param name="httpClient">The HTTP client used to perform the requests.</param>
|
|
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<StoreLogProfileEntry>();
|
|
|
|
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
|
|
|
|
/// <summary>
|
|
/// Identifies store log entries where a user registered earlier than the automatic slot creation interval.
|
|
/// </summary>
|
|
/// <param name="httpClient">The HTTP client used to perform the requests.</param>
|
|
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<int> { 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<StoreLogIntervalEntry>();
|
|
|
|
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
|
|
|
|
/// <summary>
|
|
/// Confirms all unconfirmed pickup slots for the configured Lindenberg store after user confirmation.
|
|
/// </summary>
|
|
/// <param name="httpClient">The HTTP client used to perform the requests.</param>
|
|
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
|
|
}
|
|
} |