feat: add PowerOutageLog entity, service, migration and API endpoints
This commit is contained in:
@@ -8,13 +8,16 @@ namespace GreenHome.Api.Controllers;
|
|||||||
public class PowerOutageController : ControllerBase
|
public class PowerOutageController : ControllerBase
|
||||||
{
|
{
|
||||||
private readonly IAlertService _alertService;
|
private readonly IAlertService _alertService;
|
||||||
|
private readonly IPowerOutageLogService _powerOutageLogService;
|
||||||
private readonly ILogger<PowerOutageController> _logger;
|
private readonly ILogger<PowerOutageController> _logger;
|
||||||
|
|
||||||
public PowerOutageController(
|
public PowerOutageController(
|
||||||
IAlertService alertService,
|
IAlertService alertService,
|
||||||
|
IPowerOutageLogService powerOutageLogService,
|
||||||
ILogger<PowerOutageController> logger)
|
ILogger<PowerOutageController> logger)
|
||||||
{
|
{
|
||||||
_alertService = alertService;
|
_alertService = alertService;
|
||||||
|
_powerOutageLogService = powerOutageLogService;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,9 +43,9 @@ public class PowerOutageController : ControllerBase
|
|||||||
|
|
||||||
_logger.LogInformation("Power outage alert processed for device {DeviceId}", deviceId);
|
_logger.LogInformation("Power outage alert processed for device {DeviceId}", deviceId);
|
||||||
|
|
||||||
return Ok(new {
|
return Ok(new {
|
||||||
success = true,
|
success = true,
|
||||||
message = "هشدار قطع برق با موفقیت ارسال شد"
|
message = "هشدار قطع برق با موفقیت ارسال شد"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
catch (InvalidOperationException ex)
|
catch (InvalidOperationException ex)
|
||||||
@@ -56,5 +59,175 @@ public class PowerOutageController : ControllerBase
|
|||||||
return StatusCode(500, new { error = "خطا در ارسال هشدار قطع برق" });
|
return StatusCode(500, new { error = "خطا در ارسال هشدار قطع برق" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// دریافت لیست رویدادهای قطع و وصل برق یک دستگاه
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="deviceId">شناسه دستگاه</param>
|
||||||
|
/// <param name="startUtc">شروع بازه (UTC)</param>
|
||||||
|
/// <param name="endUtc">پایان بازه (UTC)</param>
|
||||||
|
/// <param name="eventType">نوع رویداد: 1 = وصل, 2 = قطع (اختیاری)</param>
|
||||||
|
/// <param name="page">شماره صفحه</param>
|
||||||
|
/// <param name="pageSize">تعداد در هر صفحه</param>
|
||||||
|
/// <param name="cancellationToken">Cancellation token</param>
|
||||||
|
/// <returns>لیست رویدادها</returns>
|
||||||
|
[HttpGet("logs")]
|
||||||
|
public async Task<ActionResult<PagedResult<PowerOutageLogDto>>> GetLogs(
|
||||||
|
[FromQuery] int deviceId,
|
||||||
|
[FromQuery] DateTime? startUtc,
|
||||||
|
[FromQuery] DateTime? endUtc,
|
||||||
|
[FromQuery] int? eventType,
|
||||||
|
[FromQuery] int page = 1,
|
||||||
|
[FromQuery] int pageSize = 20,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (deviceId <= 0)
|
||||||
|
{
|
||||||
|
return BadRequest(new { error = "شناسه دستگاه نامعتبر است" });
|
||||||
|
}
|
||||||
|
|
||||||
|
var filter = new PowerOutageFilter
|
||||||
|
{
|
||||||
|
EventType = eventType,
|
||||||
|
StartDateUtc = startUtc,
|
||||||
|
EndDateUtc = endUtc,
|
||||||
|
Page = page,
|
||||||
|
PageSize = pageSize
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await _powerOutageLogService.ListAsync(deviceId, filter, cancellationToken);
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error getting power outage logs: DeviceId={DeviceId}", deviceId);
|
||||||
|
return StatusCode(500, new { error = "خطا در دریافت لیست رویدادهای برق" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// دریافت خلاصه قطعیهای برق یک دستگاه در یک بازه زمانی
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="deviceId">شناسه دستگاه</param>
|
||||||
|
/// <param name="startUtc">شروع بازه (UTC)</param>
|
||||||
|
/// <param name="endUtc">پایان بازه (UTC)</param>
|
||||||
|
/// <param name="cancellationToken">Cancellation token</param>
|
||||||
|
/// <returns>خلاصه قطعیها شامل تعداد قطعیها و مجموع مدت زمان قطعی</returns>
|
||||||
|
[HttpGet("summary")]
|
||||||
|
public async Task<ActionResult<PowerOutageSummaryDto>> GetSummary(
|
||||||
|
[FromQuery] int deviceId,
|
||||||
|
[FromQuery] DateTime? startUtc,
|
||||||
|
[FromQuery] DateTime? endUtc,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (deviceId <= 0)
|
||||||
|
{
|
||||||
|
return BadRequest(new { error = "شناسه دستگاه نامعتبر است" });
|
||||||
|
}
|
||||||
|
|
||||||
|
var filter = new PowerOutageFilter
|
||||||
|
{
|
||||||
|
StartDateUtc = startUtc,
|
||||||
|
EndDateUtc = endUtc,
|
||||||
|
PageSize = int.MaxValue // Get all records for summary
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await _powerOutageLogService.ListAsync(deviceId, filter, cancellationToken);
|
||||||
|
|
||||||
|
var outages = result.Items.Where(x => x.CurrentPower == 0).ToList();
|
||||||
|
var connections = result.Items.Where(x => x.CurrentPower == 1).ToList();
|
||||||
|
|
||||||
|
// Calculate total downtime by pairing disconnects with subsequent connects
|
||||||
|
long totalDowntimeSeconds = 0;
|
||||||
|
var pairedOutages = new List<PowerOutagePairDto>();
|
||||||
|
|
||||||
|
foreach (var outage in outages)
|
||||||
|
{
|
||||||
|
// Find the next connect event after this outage
|
||||||
|
var nextConnect = connections
|
||||||
|
.Where(c => c.TimestampUtc > outage.TimestampUtc)
|
||||||
|
.OrderBy(c => c.TimestampUtc)
|
||||||
|
.FirstOrDefault();
|
||||||
|
|
||||||
|
if (nextConnect != null)
|
||||||
|
{
|
||||||
|
var duration = (nextConnect.TimestampUtc - outage.TimestampUtc).TotalSeconds;
|
||||||
|
totalDowntimeSeconds += (long)duration;
|
||||||
|
|
||||||
|
pairedOutages.Add(new PowerOutagePairDto
|
||||||
|
{
|
||||||
|
DisconnectedAt = outage.TimestampUtc,
|
||||||
|
ConnectedAt = nextConnect.TimestampUtc,
|
||||||
|
DurationSeconds = (long)duration
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Still disconnected (no subsequent connect)
|
||||||
|
pairedOutages.Add(new PowerOutagePairDto
|
||||||
|
{
|
||||||
|
DisconnectedAt = outage.TimestampUtc,
|
||||||
|
ConnectedAt = null,
|
||||||
|
DurationSeconds = null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var summary = new PowerOutageSummaryDto
|
||||||
|
{
|
||||||
|
DeviceId = deviceId,
|
||||||
|
TotalOutageCount = outages.Count,
|
||||||
|
TotalDowntimeSeconds = totalDowntimeSeconds,
|
||||||
|
StartDateUtc = startUtc,
|
||||||
|
EndDateUtc = endUtc,
|
||||||
|
Outages = pairedOutages
|
||||||
|
};
|
||||||
|
|
||||||
|
return Ok(summary);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error getting power outage summary: DeviceId={DeviceId}", deviceId);
|
||||||
|
return StatusCode(500, new { error = "خطا در دریافت خلاصه قطعیهای برق" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// مهاجرت دادههای قدیمی از جدول Telemetry به PowerOutageLogs
|
||||||
|
/// این اندپوینت یکبار مصرف است - بعد از اجرا میتوانید آن را غیرفعال کنید
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="deviceId">اختیاری - اگر داده شود فقط برای همان دستگاه مهاجرت میکند</param>
|
||||||
|
/// <param name="cancellationToken">Cancellation token</param>
|
||||||
|
/// <returns>تعداد رکوردهای مهاجرت داده شده</returns>
|
||||||
|
[HttpPost("migrate")]
|
||||||
|
public async Task<ActionResult> MigrateFromTelemetry(
|
||||||
|
[FromQuery] int? deviceId,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Starting migration from Telemetry to PowerOutageLogs...");
|
||||||
|
|
||||||
|
var count = await _powerOutageLogService.MigrateFromTelemetryAsync(deviceId, cancellationToken);
|
||||||
|
|
||||||
|
_logger.LogInformation("Migration completed: {Count} records inserted", count);
|
||||||
|
|
||||||
|
return Ok(new
|
||||||
|
{
|
||||||
|
success = true,
|
||||||
|
message = $"مهاجرت با موفقیت انجام شد. {count} رکورد به جدول PowerOutageLogs اضافه شد.",
|
||||||
|
recordsInserted = count
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error during migration from Telemetry to PowerOutageLogs");
|
||||||
|
return StatusCode(500, new { error = $"خطا در مهاجرت دادهها: {ex.Message}" });
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
60
src/GreenHome.Domain/PowerOutageLog.cs
Normal file
60
src/GreenHome.Domain/PowerOutageLog.cs
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
namespace GreenHome.Domain;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// لاگ قطع و وصل برق دستگاه
|
||||||
|
/// </summary>
|
||||||
|
public sealed class PowerOutageLog
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// شناسه دستگاه
|
||||||
|
/// </summary>
|
||||||
|
public int DeviceId { get; set; }
|
||||||
|
public Device Device { get; set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// وضعیت قبلی برق (0 = قطع, 1 = وصل)
|
||||||
|
/// </summary>
|
||||||
|
public byte PreviousPower { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// وضعیت جدید برق (0 = قطع, 1 = وصل)
|
||||||
|
/// </summary>
|
||||||
|
public byte CurrentPower { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// نوع رویداد: قطع یا وصل
|
||||||
|
/// </summary>
|
||||||
|
public PowerEventType EventType { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// زمان رویداد (از دستگاه)
|
||||||
|
/// </summary>
|
||||||
|
public DateTime TimestampUtc { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// زمان ثبت در سرور
|
||||||
|
/// </summary>
|
||||||
|
public DateTime ServerTimestampUtc { get; set; }
|
||||||
|
|
||||||
|
public int PersianYear { get; set; }
|
||||||
|
public int PersianMonth { get; set; }
|
||||||
|
public string PersianDate { get; set; } = string.Empty; // yyyy/MM/dd
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// نوع رویداد برق
|
||||||
|
/// </summary>
|
||||||
|
public enum PowerEventType
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// برق وصل شده
|
||||||
|
/// </summary>
|
||||||
|
Connected = 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// برق قطع شده
|
||||||
|
/// </summary>
|
||||||
|
Disconnected = 2
|
||||||
|
}
|
||||||
1310
src/GreenHome.Infrastructure/Data/Migrations/Migrations/20260601132156_AddPowerOutageLogs.Designer.cs
generated
Normal file
1310
src/GreenHome.Infrastructure/Data/Migrations/Migrations/20260601132156_AddPowerOutageLogs.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,64 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace GreenHome.Infrastructure.Data.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddPowerOutageLogs : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "PowerOutageLogs",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "int", nullable: false)
|
||||||
|
.Annotation("SqlServer:Identity", "1, 1"),
|
||||||
|
DeviceId = table.Column<int>(type: "int", nullable: false),
|
||||||
|
PreviousPower = table.Column<byte>(type: "tinyint", nullable: false),
|
||||||
|
CurrentPower = table.Column<byte>(type: "tinyint", nullable: false),
|
||||||
|
EventType = table.Column<int>(type: "int", nullable: false),
|
||||||
|
TimestampUtc = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||||
|
ServerTimestampUtc = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||||
|
PersianYear = table.Column<int>(type: "int", nullable: false),
|
||||||
|
PersianMonth = table.Column<int>(type: "int", nullable: false),
|
||||||
|
PersianDate = table.Column<string>(type: "nvarchar(10)", maxLength: 10, nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_PowerOutageLogs", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_PowerOutageLogs_Devices_DeviceId",
|
||||||
|
column: x => x.DeviceId,
|
||||||
|
principalTable: "Devices",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_PowerOutageLogs_DeviceId_PersianYear_PersianMonth",
|
||||||
|
table: "PowerOutageLogs",
|
||||||
|
columns: new[] { "DeviceId", "PersianYear", "PersianMonth" });
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_PowerOutageLogs_DeviceId_TimestampUtc",
|
||||||
|
table: "PowerOutageLogs",
|
||||||
|
columns: new[] { "DeviceId", "TimestampUtc" });
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_PowerOutageLogs_EventType",
|
||||||
|
table: "PowerOutageLogs",
|
||||||
|
column: "EventType");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "PowerOutageLogs");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
using GreenHome.Application;
|
||||||
|
using GreenHome.Infrastructure.Data;
|
||||||
|
using GreenHome.Infrastructure.Extensions;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace GreenHome.Infrastructure.Services.PowerOutage;
|
||||||
|
|
||||||
|
public sealed class PowerOutageLogService : IPowerOutageLogService
|
||||||
|
{
|
||||||
|
private readonly GreenHomeDbContext _dbContext;
|
||||||
|
|
||||||
|
public PowerOutageLogService(GreenHomeDbContext dbContext)
|
||||||
|
{
|
||||||
|
_dbContext = dbContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task LogPowerChangeAsync(int deviceId, byte previousPower, byte currentPower, DateTime timestampUtc, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var (year, month, day, persianDate) = timestampUtc.ToPersianDate();
|
||||||
|
|
||||||
|
var log = new Domain.PowerOutageLog
|
||||||
|
{
|
||||||
|
DeviceId = deviceId,
|
||||||
|
PreviousPower = previousPower,
|
||||||
|
CurrentPower = currentPower,
|
||||||
|
EventType = currentPower == 1 ? Domain.PowerEventType.Connected : Domain.PowerEventType.Disconnected,
|
||||||
|
TimestampUtc = timestampUtc,
|
||||||
|
ServerTimestampUtc = DateTime.UtcNow,
|
||||||
|
PersianYear = year,
|
||||||
|
PersianMonth = month,
|
||||||
|
PersianDate = persianDate
|
||||||
|
};
|
||||||
|
|
||||||
|
_dbContext.PowerOutageLogs.Add(log);
|
||||||
|
await _dbContext.SaveChangesAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<PagedResult<PowerOutageLogDto>> ListAsync(int deviceId, PowerOutageFilter filter, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var query = _dbContext.PowerOutageLogs
|
||||||
|
.AsNoTracking()
|
||||||
|
.Where(x => x.DeviceId == deviceId);
|
||||||
|
|
||||||
|
if (filter.EventType.HasValue)
|
||||||
|
{
|
||||||
|
var eventType = (Domain.PowerEventType)filter.EventType.Value;
|
||||||
|
query = query.Where(x => x.EventType == eventType);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter.StartDateUtc.HasValue)
|
||||||
|
{
|
||||||
|
var start = filter.StartDateUtc.Value.ToLocalTime();
|
||||||
|
query = query.Where(x => x.ServerTimestampUtc >= start);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter.EndDateUtc.HasValue)
|
||||||
|
{
|
||||||
|
var end = filter.EndDateUtc.Value.ToLocalTime();
|
||||||
|
query = query.Where(x => x.ServerTimestampUtc < end);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter.PersianYear.HasValue)
|
||||||
|
{
|
||||||
|
query = query.Where(x => x.PersianYear == filter.PersianYear.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter.PersianMonth.HasValue)
|
||||||
|
{
|
||||||
|
query = query.Where(x => x.PersianMonth == filter.PersianMonth.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter.Page <= 0) filter.Page = 1;
|
||||||
|
if (filter.PageSize <= 0) filter.PageSize = 20;
|
||||||
|
|
||||||
|
var total = await query.CountAsync(cancellationToken);
|
||||||
|
var skip = (filter.Page - 1) * filter.PageSize;
|
||||||
|
|
||||||
|
var items = await query
|
||||||
|
.OrderByDescending(x => x.TimestampUtc)
|
||||||
|
.Skip(skip)
|
||||||
|
.Take(filter.PageSize)
|
||||||
|
.Select(x => new PowerOutageLogDto
|
||||||
|
{
|
||||||
|
Id = x.Id,
|
||||||
|
DeviceId = x.DeviceId,
|
||||||
|
DeviceName = x.Device.DeviceName,
|
||||||
|
PreviousPower = x.PreviousPower,
|
||||||
|
CurrentPower = x.CurrentPower,
|
||||||
|
EventType = x.EventType.ToString(),
|
||||||
|
EventTypePersian = x.EventType == Domain.PowerEventType.Connected ? "وصل" : "قطع",
|
||||||
|
TimestampUtc = x.TimestampUtc,
|
||||||
|
ServerTimestampUtc = x.ServerTimestampUtc,
|
||||||
|
PersianDate = x.PersianDate
|
||||||
|
})
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
return new PagedResult<PowerOutageLogDto>
|
||||||
|
{
|
||||||
|
Items = items,
|
||||||
|
TotalCount = total,
|
||||||
|
Page = filter.Page,
|
||||||
|
PageSize = filter.PageSize
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<int> MigrateFromTelemetryAsync(int? deviceId, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var query = _dbContext.TelemetryRecords
|
||||||
|
.Where(x => x.Power != null);
|
||||||
|
|
||||||
|
if (deviceId.HasValue)
|
||||||
|
{
|
||||||
|
query = query.Where(x => x.DeviceId == deviceId.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all relevant telemetry records grouped by device, ordered by timestamp
|
||||||
|
var telemetryGroups = await query
|
||||||
|
.OrderBy(x => x.DeviceId)
|
||||||
|
.ThenBy(x => x.TimestampUtc)
|
||||||
|
.Select(x => new { x.Id, x.DeviceId, x.Power, x.TimestampUtc })
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
// Group by device and detect power changes
|
||||||
|
var groups = telemetryGroups.GroupBy(x => x.DeviceId);
|
||||||
|
var totalInserted = 0;
|
||||||
|
|
||||||
|
foreach (var group in groups)
|
||||||
|
{
|
||||||
|
Domain.PowerOutageLog? previousLog = null;
|
||||||
|
|
||||||
|
foreach (var record in group)
|
||||||
|
{
|
||||||
|
if (previousLog != null && previousLog.CurrentPower != record.Power)
|
||||||
|
{
|
||||||
|
// Power status changed - insert a log
|
||||||
|
var (year, month, day, persianDate) = record.TimestampUtc.ToPersianDate();
|
||||||
|
|
||||||
|
var log = new Domain.PowerOutageLog
|
||||||
|
{
|
||||||
|
DeviceId = record.DeviceId,
|
||||||
|
PreviousPower = previousLog.CurrentPower,
|
||||||
|
CurrentPower = record.Power.Value,
|
||||||
|
EventType = record.Power == 1 ? Domain.PowerEventType.Connected : Domain.PowerEventType.Disconnected,
|
||||||
|
TimestampUtc = record.TimestampUtc,
|
||||||
|
ServerTimestampUtc = DateTime.UtcNow,
|
||||||
|
PersianYear = year,
|
||||||
|
PersianMonth = month,
|
||||||
|
PersianDate = persianDate
|
||||||
|
};
|
||||||
|
|
||||||
|
_dbContext.PowerOutageLogs.Add(log);
|
||||||
|
totalInserted++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track last known power state for this device
|
||||||
|
previousLog = new Domain.PowerOutageLog
|
||||||
|
{
|
||||||
|
DeviceId = record.DeviceId,
|
||||||
|
CurrentPower = record.Power.Value,
|
||||||
|
TimestampUtc = record.TimestampUtc
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await _dbContext.SaveChangesAsync(cancellationToken);
|
||||||
|
return totalInserted;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user