Add AnalyzeEarlySlotRegistrationsAsync method and update Program menu
This commit is contained in:
@@ -159,6 +159,105 @@ namespace FsToolbox.Cli
|
|||||||
|
|
||||||
#endregion
|
#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
|
#region Public Method ConfirmUnconfirmedPickupsLindenbergAsync
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ logger.Info("Choose a task to execute:");
|
|||||||
logger.Info("1. Check Aldi Memberships");
|
logger.Info("1. Check Aldi Memberships");
|
||||||
logger.Info("2. Confirm all Unconfirmed Pickups for Lindenberg");
|
logger.Info("2. Confirm all Unconfirmed Pickups for Lindenberg");
|
||||||
logger.Info("3. Analyze slot unregistrations for region Siegen");
|
logger.Info("3. Analyze slot unregistrations for region Siegen");
|
||||||
|
logger.Info("4. Analyze early slot registrations (calendar interval)");
|
||||||
logger.Info("Enter the number of the task to execute (or any other key to exit): ");
|
logger.Info("Enter the number of the task to execute (or any other key to exit): ");
|
||||||
var choice = Console.ReadLine();
|
var choice = Console.ReadLine();
|
||||||
|
|
||||||
@@ -33,6 +34,9 @@ switch (choice)
|
|||||||
case "3":
|
case "3":
|
||||||
await CustomTasks.AnalyzeSlotUnregistrationsAsync(httpClient);
|
await CustomTasks.AnalyzeSlotUnregistrationsAsync(httpClient);
|
||||||
break;
|
break;
|
||||||
|
case "4":
|
||||||
|
await CustomTasks.AnalyzeEarlySlotRegistrationsAsync(httpClient);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
logger.Info("Exiting...");
|
logger.Info("Exiting...");
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -82,6 +82,47 @@ namespace FsToolbox.Cli.Tasks
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Public Method GetStoreInformationAsync
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves store information including the calendar interval.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="httpClient">The HTTP client used to send the request.</param>
|
||||||
|
/// <param name="storeId">The store identifier to query.</param>
|
||||||
|
/// <returns>The store information, or null when the call fails.</returns>
|
||||||
|
public static async Task<StoreInformation?> GetStoreInformationAsync(HttpClient httpClient, int storeId)
|
||||||
|
{
|
||||||
|
await AuthHelper.EnsureAuthenticationAsync(httpClient);
|
||||||
|
|
||||||
|
var uri = string.Format(Endpoints.StoreInformation, storeId);
|
||||||
|
var response = await httpClient.GetAsync(uri);
|
||||||
|
var responseBody = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
Logger.Error("Store information retrieval failed ({Status} {Reason}): {Body}", (int)response.StatusCode, response.ReasonPhrase, responseBody);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var opts = new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
PropertyNameCaseInsensitive = true,
|
||||||
|
PropertyNamingPolicy = null
|
||||||
|
};
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return JsonSerializer.Deserialize<StoreInformation>(responseBody, opts);
|
||||||
|
}
|
||||||
|
catch (JsonException ex)
|
||||||
|
{
|
||||||
|
Logger.Error(ex, "Failed to parse store information response.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region Public Method GetStoreLogAsync
|
#region Public Method GetStoreLogAsync
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
Reference in New Issue
Block a user