added pause and resume alarm diagnosis review campaign service

This commit is contained in:
Yinyin Liu 2026-02-26 08:25:15 +01:00
parent e72f16f26b
commit 062fd5141f
2 changed files with 93 additions and 3 deletions

View File

@ -1275,7 +1275,21 @@ public class Controller : ControllerBase
public ActionResult StopAlarmReviewCampaign() public ActionResult StopAlarmReviewCampaign()
{ {
AlarmReviewService.StopCampaign(); AlarmReviewService.StopCampaign();
return Ok(new { message = "Campaign stopped and progress file deleted. Safe to redeploy." }); return Ok(new { message = "Campaign paused — progress preserved. Use ResumeAlarmReviewCampaign to restart timers." });
}
[HttpPost(nameof(ResumeAlarmReviewCampaign))]
public ActionResult ResumeAlarmReviewCampaign()
{
AlarmReviewService.ResumeCampaign();
return Ok(new { message = "Campaign resumed — timers restarted from existing progress." });
}
[HttpPost(nameof(ResetAlarmReviewCampaign))]
public ActionResult ResetAlarmReviewCampaign()
{
AlarmReviewService.ResetCampaign();
return Ok(new { message = "Campaign fully reset — all progress deleted. Use StartAlarmReviewCampaign to begin again." });
} }
[HttpGet(nameof(CorrectAlarm))] [HttpGet(nameof(CorrectAlarm))]

View File

@ -206,8 +206,33 @@ public static class AlarmReviewService
Console.WriteLine("[AlarmReviewService] Daily scheduler started (8AM + 2PM jobs)."); Console.WriteLine("[AlarmReviewService] Daily scheduler started (8AM + 2PM jobs).");
} }
/// <summary>Stops the scheduler and deletes the progress file. Safe to call at any time.</summary> /// <summary>Pauses the scheduler (timers stopped) but keeps all progress. Resume with ResumeCampaign().</summary>
public static void StopCampaign() public static void StopCampaign()
{
_morningTimer?.Dispose();
_afternoonTimer?.Dispose();
_morningTimer = null;
_afternoonTimer = null;
_testBatch = null;
Console.WriteLine("[AlarmReviewService] Campaign paused — progress preserved. Call ResumeCampaign() to restart timers.");
}
/// <summary>Resumes timers without touching progress. Use after StopCampaign() or server restart.</summary>
public static void ResumeCampaign()
{
if (!File.Exists(ProgressFile))
{
Console.WriteLine("[AlarmReviewService] No progress file found — use StartCampaign() to begin.");
return;
}
LoadGermanNames();
ScheduleTimer(ref _morningTimer, 8, 0, () => RunMorningJobAsync() .GetAwaiter().GetResult());
ScheduleTimer(ref _afternoonTimer, 14, 0, () => RunAfternoonJobAsync().GetAwaiter().GetResult());
Console.WriteLine("[AlarmReviewService] Campaign resumed — timers restarted.");
}
/// <summary>Deletes all progress and stops timers. Only use to start over from scratch.</summary>
public static void ResetCampaign()
{ {
_morningTimer?.Dispose(); _morningTimer?.Dispose();
_afternoonTimer?.Dispose(); _afternoonTimer?.Dispose();
@ -217,8 +242,10 @@ public static class AlarmReviewService
if (File.Exists(ProgressFile)) if (File.Exists(ProgressFile))
File.Delete(ProgressFile); File.Delete(ProgressFile);
if (File.Exists(CheckedFilePath))
File.Delete(CheckedFilePath);
Console.WriteLine("[AlarmReviewService] Campaign stopped and progress file deleted."); Console.WriteLine("[AlarmReviewService] Campaign fully reset — all progress deleted.");
} }
private static void ScheduleTimer(ref Timer? timer, int hour, int minute, Action action) private static void ScheduleTimer(ref Timer? timer, int hour, int minute, Action action)
@ -265,6 +292,15 @@ public static class AlarmReviewService
{ {
var submissionCount = current.Submissions.Values.Count(s => s != null); var submissionCount = current.Submissions.Values.Count(s => s != null);
// If the batch was sent today, don't treat 0 submissions as a stall —
// reviewers haven't had a full day yet. Skip and wait for tomorrow's job.
var sentToday = current.SentDate == DateTime.Now.ToString("yyyy-MM-dd");
if (submissionCount == 0 && sentToday)
{
Console.WriteLine($"[AlarmReviewService] Batch {current.BatchNumber}: sent today, 0 submissions so far — waiting for tomorrow.");
return;
}
if (submissionCount == 0) if (submissionCount == 0)
{ {
const int MaxResends = 3; const int MaxResends = 3;
@ -1457,6 +1493,46 @@ render();
var html = BuildReviewerEmailHtml(name, reviewUrl, batch.BatchNumber, batch.AlarmKeys.Count, quote, isResend); var html = BuildReviewerEmailHtml(name, reviewUrl, batch.BatchNumber, batch.AlarmKeys.Count, quote, isResend);
await SendEmailAsync(email, subject, html); await SendEmailAsync(email, subject, html);
} }
// Notify admin with a preview of what reviewers received
await SendAdminBatchDispatchNoticeAsync(batch, isResend);
}
private static async Task SendAdminBatchDispatchNoticeAsync(BatchRecord batch, bool isResend)
{
var totalBatches = (int)Math.Ceiling((double)AllAlarmKeys.Length / BatchSize);
var action = isResend ? "erneut gesendet" : "gesendet";
var subject = $"[Admin] Stapel {batch.BatchNumber}/{totalBatches} {action} an {Reviewers.Length} Prüfer";
var previewUrl = $"{BaseUrl}/ReviewAlarms?batch={batch.BatchNumber}&reviewer={Uri.EscapeDataString(Reviewers[0].Name)}";
var reviewerList = string.Join(", ", Reviewers.Select(r => r.Name));
var alarmRows = string.Join("\n", batch.AlarmKeys.Select((key, i) =>
$"""<tr><td style="padding:4px 12px;border-bottom:1px solid #f5f5f5;font-size:12px;color:#555">{i + 1}. {SplitCamelCase(key)} <span style="color:#aaa;font-size:11px">({(SinexcelKeySet.Contains(key) ? "Sinexcel" : "Growatt")})</span></td></tr>"""));
var html = $"""
<!DOCTYPE html><html><head><meta charset="utf-8"></head>
<body style="font-family:Arial,Helvetica,sans-serif;font-size:14px;color:#333;padding:24px;max-width:650px">
<h2 style="color:#2c3e50;margin-bottom:4px">Stapel {batch.BatchNumber} {action}</h2>
<p style="color:#888;font-size:12px;margin-bottom:20px">{DateTime.Now:yyyy-MM-dd HH:mm} · {batch.AlarmKeys.Count} Alarme · Deadline: 8:00 Uhr morgen früh</p>
<p style="margin-bottom:16px">Gesendet an: <strong>{reviewerList}</strong></p>
<p style="margin-bottom:20px">
<a href="{previewUrl}" style="background:#3498db;color:#fff;text-decoration:none;padding:9px 22px;border-radius:6px;font-size:13px;display:inline-block">Formular ansehen </a>
<span style="font-size:12px;color:#888;margin-left:10px">(alle Prüfer sehen denselben Inhalt)</span>
</p>
<h3 style="color:#2c3e50;margin-bottom:8px">Alarme in diesem Stapel</h3>
<table style="border-collapse:collapse;width:100%;margin-bottom:24px">
{alarmRows}
</table>
<p style="font-size:11px;color:#bbb">Diese E-Mail dient nur zur Information keine Aktion erforderlich.</p>
</body></html>
""";
await SendEmailAsync(AdminEmail, subject, html);
} }
private static async Task SendReminderEmailAsync(BatchRecord batch, string name, string email) private static async Task SendReminderEmailAsync(BatchRecord batch, string name, string email)