diff --git a/src/GreenHome.Api/Controllers/AlertConditionsController.cs b/src/GreenHome.Api/Controllers/AlertConditionsController.cs
index ef0593e..bf85bf9 100644
--- a/src/GreenHome.Api/Controllers/AlertConditionsController.cs
+++ b/src/GreenHome.Api/Controllers/AlertConditionsController.cs
@@ -81,5 +81,15 @@ public class AlertConditionsController : ControllerBase
}
return Ok();
}
+
+ ///
+ /// دریافت شرایط هشدار رلهای (notificationType > 0) برای یک دستگاه
+ ///
+ [HttpGet("relay/{deviceId}")]
+ public async Task> GetRelayConditions(int deviceId, CancellationToken cancellationToken)
+ {
+ var conditions = await alertConditionService.GetRelayConditionsAsync(deviceId, cancellationToken);
+ return Ok(new { conditions });
+ }
}
diff --git a/src/GreenHome.Api/Controllers/TelemetryController.cs b/src/GreenHome.Api/Controllers/TelemetryController.cs
index f009a97..f519120 100644
--- a/src/GreenHome.Api/Controllers/TelemetryController.cs
+++ b/src/GreenHome.Api/Controllers/TelemetryController.cs
@@ -9,11 +9,13 @@ public class TelemetryController : ControllerBase
{
private readonly ITelemetryService telemetryService;
private readonly IAlertService alertService;
+ private readonly IAlertConditionService alertConditionService;
- public TelemetryController(ITelemetryService telemetryService, IAlertService alertService)
+ public TelemetryController(ITelemetryService telemetryService, IAlertService alertService, IAlertConditionService alertConditionService)
{
this.telemetryService = telemetryService;
this.alertService = alertService;
+ this.alertConditionService = alertConditionService;
}
[HttpGet]
@@ -25,7 +27,7 @@ public class TelemetryController : ControllerBase
}
[HttpGet("AddData")]
- public async Task> Create([FromQuery] TelemetryDto telementry, CancellationToken cancellationToken)
+ public async Task> Create([FromQuery] TelemetryDto telementry, [FromQuery] string? lastUpdateDate, CancellationToken cancellationToken)
{
telementry.TimestampUtc = DateTime.UtcNow;
@@ -43,28 +45,47 @@ public class TelemetryController : ControllerBase
}
}
- // Check and send alerts if needed (fire and forget)
- // _ = Task.Run(async () =>
- // {
- // try
- // {
- // // Get deviceId from the saved telemetry record
- // var deviceId = telementry.DeviceId;
- // if (deviceId > 0)
- // {
- // await alertService.CheckAndSendAlertsAsync(deviceId, telementry, cancellationToken);
- // }
- // }
- // catch
- // {
- // // Log error but don't fail the request
- // // Errors are logged in AlertService
- // }
- // }, cancellationToken);
+ // Check if relay conditions have been updated since lastUpdateDate
+ if (deviceId > 0 && !string.IsNullOrEmpty(lastUpdateDate))
+ {
+ var parsedDate = ParseLastUpdateDate(lastUpdateDate);
+ // var relayConditions = await alertConditionService.GetUpdatedRelayConditionsAsync(deviceId, parsedDate, cancellationToken);
+ // if (relayConditions.Count > 0)
+ // {
+ // return Ok(new { conditions = relayConditions });
+ // }
+
+ var hasUpdates = await alertConditionService.HasUpdatedConditionsAsync(deviceId, parsedDate, cancellationToken);
+ if(hasUpdates)
+ return Ok("Relay");
+ }
return Ok(id + "");
}
+ ///
+ /// Parse date string in format: yyyy/MM/dd-HH:mm
+ ///
+ private static DateTime? ParseLastUpdateDate(string dateStr)
+ {
+ if (string.IsNullOrEmpty(dateStr))
+ return null;
+
+ // Try parsing as yyyy/MM/dd-HH:mm
+ if (DateTime.TryParseExact(dateStr, "yyyy/MM/dd-HH:mm", null, System.Globalization.DateTimeStyles.None, out var result))
+ {
+ return DateTime.SpecifyKind(result, DateTimeKind.Utc);
+ }
+
+ // Try parsing as yyyy-MM-dd HH:mm (ISO-like)
+ if (DateTime.TryParseExact(dateStr, "yyyy-MM-dd HH:mm", null, System.Globalization.DateTimeStyles.None, out var result2))
+ {
+ return DateTime.SpecifyKind(result2, DateTimeKind.Utc);
+ }
+
+ return null;
+ }
+
[HttpGet("minmax")]
public async Task> MinMax([FromQuery] int deviceId, [FromQuery] DateTime? startUtc, [FromQuery] DateTime? endUtc, CancellationToken cancellationToken)
{
diff --git a/src/GreenHome.Application/Dtos.cs b/src/GreenHome.Application/Dtos.cs
index 66f4516..c7e05bb 100644
--- a/src/GreenHome.Application/Dtos.cs
+++ b/src/GreenHome.Application/Dtos.cs
@@ -136,6 +136,7 @@ public sealed class DeviceSettingsDto
public int UploadIntervalMin { get; set; } = 5;
public string DevicePhoneNumber { get; set; } = string.Empty;
public Domain.SimCardType? SimCardType { get; set; }
+ public int RelayCount { get; set; } = 4;
public string SmsRecievers { get; set; } = string.Empty;
public string? TokenCode { get; set; }
public string? VerificationCode { get; set; }
@@ -171,6 +172,28 @@ public sealed class AlertRuleDto
public int Order { get; set; }
}
+///
+/// DTO for relay alert conditions (notificationType > 0)
+///
+public sealed class RelayConditionDto
+{
+ public int Enabled { get; set; }
+ public int TimeType { get; set; }
+ public string Relay { get; set; } = string.Empty;
+ public List Rules { get; set; } = new();
+}
+
+///
+/// DTO for relay condition rules
+///
+public sealed class RelayRuleDto
+{
+ public string Sensor { get; set; } = string.Empty;
+ public string Op { get; set; } = string.Empty;
+ public decimal V1 { get; set; }
+ public decimal? V2 { get; set; }
+}
+
public sealed class CreateAlertConditionRequest
{
public required int DeviceId { get; set; }
diff --git a/src/GreenHome.Application/IAlertConditionService.cs b/src/GreenHome.Application/IAlertConditionService.cs
index 4b4ea09..c532eda 100644
--- a/src/GreenHome.Application/IAlertConditionService.cs
+++ b/src/GreenHome.Application/IAlertConditionService.cs
@@ -8,5 +8,18 @@ public interface IAlertConditionService
Task UpdateAsync(UpdateAlertConditionRequest request, CancellationToken cancellationToken);
Task DeleteAsync(int id, CancellationToken cancellationToken);
Task ToggleEnabledAsync(int id, bool isEnabled, CancellationToken cancellationToken);
+
+ ///
+ /// دریافت شرایط هشدار رلهای (notificationType > 0) برای یک دستگاه
+ ///
+ Task> GetRelayConditionsAsync(int deviceId, CancellationToken cancellationToken);
+
+ ///
+ /// دریافت شرایط هشدار رلهای که بعد از تاریخ مشخصی بهروزرسانی شدهاند
+ /// اگر هیچ رکوردی بهروزرسانی نشده باشد، لیست خالی برمیگرداند
+ ///
+ Task> GetUpdatedRelayConditionsAsync(int deviceId, DateTime? lastUpdateDate, CancellationToken cancellationToken);
+
+ Task HasUpdatedConditionsAsync(int deviceId, DateTime? lastUpdateDate, CancellationToken cancellationToken);
}
diff --git a/src/GreenHome.Domain/DeviceSettings.cs b/src/GreenHome.Domain/DeviceSettings.cs
index 3244c59..8bb7eae 100644
--- a/src/GreenHome.Domain/DeviceSettings.cs
+++ b/src/GreenHome.Domain/DeviceSettings.cs
@@ -84,6 +84,11 @@ public sealed class DeviceSettings
///
public SimCardType? SimCardType { get; set; }
+ ///
+ /// تعداد رلهها
+ ///
+ public int RelayCount { get; set; } = 4;
+
[StringLength(120)]
public string SmsRecievers { get; set; } = string.Empty;
diff --git a/src/GreenHome.Infrastructure/AlertConditionService.cs b/src/GreenHome.Infrastructure/AlertConditionService.cs
index 095acf8..c09ba7f 100644
--- a/src/GreenHome.Infrastructure/AlertConditionService.cs
+++ b/src/GreenHome.Infrastructure/AlertConditionService.cs
@@ -127,5 +127,60 @@ public sealed class AlertConditionService : IAlertConditionService
await dbContext.SaveChangesAsync(cancellationToken);
return true;
}
+
+ public async Task> GetRelayConditionsAsync(int deviceId, CancellationToken cancellationToken)
+ {
+ var conditions = await dbContext.AlertConditions
+ .Include(x => x.Rules)
+ .Where(x => x.DeviceId == deviceId && (int)x.NotificationType > 0)
+ .OrderByDescending(x => x.CreatedAt)
+ .AsNoTracking()
+ .ToListAsync(cancellationToken);
+
+ var result = conditions.Select(c => new RelayConditionDto
+ {
+ Enabled = c.IsEnabled ? 1 : 0,
+ TimeType = (int)c.TimeType,
+ Relay = "R" + (int)c.NotificationType,
+ Rules = c.Rules.OrderBy(r => r.Order).Select(r => new RelayRuleDto
+ {
+ Sensor = ((int)r.SensorType).ToString(),
+ Op = ((int)r.ComparisonType).ToString(),
+ V1 = r.Value1,
+ V2 = r.Value2
+ }).ToList()
+ }).ToList();
+
+ return result;
+ }
+
+ public async Task> GetUpdatedRelayConditionsAsync(int deviceId, DateTime? lastUpdateDate, CancellationToken cancellationToken)
+ {
+ // اگر lastUpdateDate null باشه، یعنی کلاینت تا حالا هیچ دادهای نگرفته، همه رو برگردون
+ if (lastUpdateDate == null)
+ {
+ return await GetRelayConditionsAsync(deviceId, cancellationToken);
+ }
+
+ var hasUpdates = await dbContext.AlertConditions
+ .AnyAsync(x => x.DeviceId == deviceId && (int)x.NotificationType > 0 && x.UpdatedAt > lastUpdateDate, cancellationToken);
+
+ if (!hasUpdates)
+ {
+ return Array.Empty();
+ }
+
+ return await GetRelayConditionsAsync(deviceId, cancellationToken);
+ }
+
+ public async Task HasUpdatedConditionsAsync(int deviceId, DateTime? lastUpdateDate, CancellationToken cancellationToken)
+ {
+ if(lastUpdateDate == null) return true;
+
+ var hasUpdates = await dbContext.AlertConditions
+ .AnyAsync(x => x.DeviceId == deviceId && (int)x.NotificationType > 0 && x.UpdatedAt > lastUpdateDate, cancellationToken);
+
+ return hasUpdates;
+ }
}
diff --git a/src/GreenHome.Infrastructure/AlertService.cs b/src/GreenHome.Infrastructure/AlertService.cs
index 7016ded..cac755b 100644
--- a/src/GreenHome.Infrastructure/AlertService.cs
+++ b/src/GreenHome.Infrastructure/AlertService.cs
@@ -112,16 +112,20 @@ public sealed class AlertService : IAlertService
// Check if all rules match (AND logic)
var allRulesMatch = condition.Rules.All(rule => CheckRule(rule, telemetry));
- if (allRulesMatch && condition.Rules.Any())
+ if (allRulesMatch && condition.Rules.Count != 0)
{
// All rules passed, send alert if cooldown period has passed
// await SendAlertForConditionAsync(condition, device, usersToAlert, telemetry, cancellationToken);
- string? message = await GetAlertMessage(condition, device, device.User, telemetry, cancellationToken);
- if(message != null && message.Length > 0)
- messages.Add(message);
+ AlertNotification notif = await GetAlertMessage(condition, device, device.User, telemetry, cancellationToken);
+ if(notif != null && !string.IsNullOrEmpty(notif.Message))
+ {
+ messages.Add(notif.Message);
+ await dbContext.AlertNotifications.AddAsync(notif, cancellationToken);
+ }
// return message;
}
}
+ await dbContext.SaveChangesAsync(cancellationToken);
if(telemetry.OldPower == 0 && telemetry.Power == 1)
{
@@ -163,7 +167,7 @@ public sealed class AlertService : IAlertService
};
}
- private async Task GetAlertMessage(
+ private async Task GetAlertMessage(
Domain.AlertCondition condition,
Domain.Device device,
User user,
@@ -204,12 +208,12 @@ public sealed class AlertService : IAlertService
//MessageOutboxIds = messageOutboxIds,
//ErrorMessage = errorMessage,
SentAt = sentAt,
- IsSent = true
+ //IsSent = true
};
- dbContext.AlertNotifications.Add(notification);
+ // dbContext.AlertNotifications.Add(notification);
- return message;
+ return notification;
}
private async Task SendAlertForConditionAsync(
diff --git a/src/GreenHome.Infrastructure/GreenHomeDbContext.cs b/src/GreenHome.Infrastructure/GreenHomeDbContext.cs
index 3e22b8c..8916f1b 100644
--- a/src/GreenHome.Infrastructure/GreenHomeDbContext.cs
+++ b/src/GreenHome.Infrastructure/GreenHomeDbContext.cs
@@ -75,6 +75,7 @@ public sealed class GreenHomeDbContext : DbContext
b.Property(x => x.MinimumCallIntervalMinutes).HasDefaultValue(60);
b.Property(x => x.AreaSquareMeters).HasColumnType("decimal(18,2)");
b.Property(x => x.SmsRecievers).HasMaxLength(120);
+ b.Property(x => x.RelayCount).HasDefaultValue(4);
b.HasOne(x => x.Device)
.WithMany()
.HasForeignKey(x => x.DeviceId)
diff --git a/src/GreenHome.Infrastructure/Migrations/20260428054608_AddRelayCountToDeviceSettings.Designer.cs b/src/GreenHome.Infrastructure/Migrations/20260428054608_AddRelayCountToDeviceSettings.Designer.cs
new file mode 100644
index 0000000..c3a83c6
--- /dev/null
+++ b/src/GreenHome.Infrastructure/Migrations/20260428054608_AddRelayCountToDeviceSettings.Designer.cs
@@ -0,0 +1,1251 @@
+//
+using System;
+using GreenHome.Infrastructure;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace GreenHome.Infrastructure.Migrations
+{
+ [DbContext(typeof(GreenHomeDbContext))]
+ [Migration("20260428054608_AddRelayCountToDeviceSettings")]
+ partial class AddRelayCountToDeviceSettings
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "9.0.9")
+ .HasAnnotation("Relational:MaxIdentifierLength", 128);
+
+ SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
+
+ modelBuilder.Entity("GreenHome.Domain.AIQuery", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("Answer")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("CompletionTokens")
+ .HasColumnType("int");
+
+ b.Property("CreatedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("DeviceId")
+ .HasColumnType("int");
+
+ b.Property("Model")
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.Property("PromptTokens")
+ .HasColumnType("int");
+
+ b.Property("Question")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("ResponseTimeMs")
+ .HasColumnType("bigint");
+
+ b.Property("Temperature")
+ .HasColumnType("float");
+
+ b.Property("TotalTokens")
+ .HasColumnType("int");
+
+ b.Property("UserId")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CreatedAt");
+
+ b.HasIndex("DeviceId");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AIQueries", (string)null);
+ });
+
+ modelBuilder.Entity("GreenHome.Domain.AlertCondition", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("CallCooldownMinutes")
+ .HasColumnType("int");
+
+ b.Property("CreatedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("DeviceId")
+ .HasColumnType("int");
+
+ b.Property("IsEnabled")
+ .HasColumnType("bit");
+
+ b.Property("NotificationType")
+ .HasColumnType("int");
+
+ b.Property("SmsCooldownMinutes")
+ .HasColumnType("int");
+
+ b.Property("TimeType")
+ .HasColumnType("int");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("datetime2");
+
+ b.HasKey("Id");
+
+ b.HasIndex("DeviceId");
+
+ b.ToTable("AlertConditions", (string)null);
+ });
+
+ modelBuilder.Entity("GreenHome.Domain.AlertLog", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("AlertConditionId")
+ .HasColumnType("int");
+
+ b.Property("AlertType")
+ .HasColumnType("int");
+
+ b.Property("DeviceId")
+ .HasColumnType("int");
+
+ b.Property("ErrorMessage")
+ .HasMaxLength(2000)
+ .HasColumnType("nvarchar(2000)");
+
+ b.Property("Message")
+ .IsRequired()
+ .HasMaxLength(1000)
+ .HasColumnType("nvarchar(1000)");
+
+ b.Property("NotificationType")
+ .HasColumnType("int");
+
+ b.Property("PhoneNumber")
+ .IsRequired()
+ .HasMaxLength(20)
+ .HasColumnType("nvarchar(20)");
+
+ b.Property("ProcessingTimeMs")
+ .HasColumnType("bigint");
+
+ b.Property("SentAt")
+ .HasColumnType("datetime2");
+
+ b.Property("Status")
+ .HasColumnType("int");
+
+ b.Property("UserId")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AlertConditionId");
+
+ b.HasIndex("AlertType");
+
+ b.HasIndex("Status");
+
+ b.HasIndex("DeviceId", "SentAt");
+
+ b.HasIndex("UserId", "SentAt");
+
+ b.ToTable("AlertLogs", (string)null);
+ });
+
+ modelBuilder.Entity("GreenHome.Domain.AlertNotification", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("AlertConditionId")
+ .HasColumnType("int");
+
+ b.Property("DeviceId")
+ .HasColumnType("int");
+
+ b.Property("ErrorMessage")
+ .HasMaxLength(1000)
+ .HasColumnType("nvarchar(1000)");
+
+ b.Property("IsSent")
+ .HasColumnType("bit");
+
+ b.Property("Message")
+ .IsRequired()
+ .HasMaxLength(500)
+ .HasColumnType("nvarchar(500)");
+
+ b.Property("MessageOutboxIds")
+ .HasMaxLength(500)
+ .HasColumnType("nvarchar(500)");
+
+ b.Property("NotificationType")
+ .HasColumnType("int");
+
+ b.Property("SentAt")
+ .HasColumnType("datetime2");
+
+ b.Property("UserId")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AlertConditionId");
+
+ b.HasIndex("UserId");
+
+ b.HasIndex("DeviceId", "UserId", "AlertConditionId", "SentAt");
+
+ b.ToTable("AlertNotifications", (string)null);
+ });
+
+ modelBuilder.Entity("GreenHome.Domain.AlertRule", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("AlertConditionId")
+ .HasColumnType("int");
+
+ b.Property("ComparisonType")
+ .HasColumnType("int");
+
+ b.Property("Order")
+ .HasColumnType("int");
+
+ b.Property("SensorType")
+ .HasColumnType("int");
+
+ b.Property("Value1")
+ .HasColumnType("decimal(18,2)");
+
+ b.Property("Value2")
+ .HasColumnType("decimal(18,2)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AlertConditionId");
+
+ b.ToTable("AlertRules", (string)null);
+ });
+
+ modelBuilder.Entity("GreenHome.Domain.Checklist", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("CreatedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("CreatedByUserId")
+ .HasColumnType("int");
+
+ b.Property("Description")
+ .HasMaxLength(1000)
+ .HasColumnType("nvarchar(1000)");
+
+ b.Property("DeviceId")
+ .HasColumnType("int");
+
+ b.Property("IsActive")
+ .HasColumnType("bit");
+
+ b.Property("Title")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("nvarchar(200)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CreatedByUserId");
+
+ b.HasIndex("DeviceId", "IsActive");
+
+ b.ToTable("Checklists", (string)null);
+ });
+
+ modelBuilder.Entity("GreenHome.Domain.ChecklistCompletion", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("ChecklistId")
+ .HasColumnType("int");
+
+ b.Property("CompletedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("CompletedByUserId")
+ .HasColumnType("int");
+
+ b.Property("Notes")
+ .HasMaxLength(2000)
+ .HasColumnType("nvarchar(2000)");
+
+ b.Property("PersianDate")
+ .IsRequired()
+ .HasMaxLength(10)
+ .HasColumnType("nvarchar(10)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CompletedByUserId");
+
+ b.HasIndex("ChecklistId", "PersianDate");
+
+ b.ToTable("ChecklistCompletions", (string)null);
+ });
+
+ modelBuilder.Entity("GreenHome.Domain.ChecklistItem", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("ChecklistId")
+ .HasColumnType("int");
+
+ b.Property("Description")
+ .HasMaxLength(1000)
+ .HasColumnType("nvarchar(1000)");
+
+ b.Property("IsRequired")
+ .HasColumnType("bit");
+
+ b.Property("Order")
+ .HasColumnType("int");
+
+ b.Property("Title")
+ .IsRequired()
+ .HasMaxLength(300)
+ .HasColumnType("nvarchar(300)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ChecklistId", "Order");
+
+ b.ToTable("ChecklistItems", (string)null);
+ });
+
+ modelBuilder.Entity("GreenHome.Domain.ChecklistItemCompletion", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("ChecklistCompletionId")
+ .HasColumnType("int");
+
+ b.Property("ChecklistItemId")
+ .HasColumnType("int");
+
+ b.Property("IsChecked")
+ .HasColumnType("bit");
+
+ b.Property("Note")
+ .HasMaxLength(500)
+ .HasColumnType("nvarchar(500)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ChecklistCompletionId");
+
+ b.HasIndex("ChecklistItemId");
+
+ b.ToTable("ChecklistItemCompletions", (string)null);
+ });
+
+ modelBuilder.Entity("GreenHome.Domain.DailyReport", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("Analysis")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("CompletionTokens")
+ .HasColumnType("int");
+
+ b.Property("CreatedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("DeviceId")
+ .HasColumnType("int");
+
+ b.Property("Model")
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.Property("PersianDate")
+ .IsRequired()
+ .HasMaxLength(10)
+ .HasColumnType("nvarchar(10)");
+
+ b.Property("PersianDay")
+ .HasColumnType("int");
+
+ b.Property("PersianMonth")
+ .HasColumnType("int");
+
+ b.Property("PersianYear")
+ .HasColumnType("int");
+
+ b.Property("PromptTokens")
+ .HasColumnType("int");
+
+ b.Property("RecordCount")
+ .HasColumnType("int");
+
+ b.Property("ResponseTimeMs")
+ .HasColumnType("bigint");
+
+ b.Property("SampledRecordCount")
+ .HasColumnType("int");
+
+ b.Property("TotalTokens")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CreatedAt");
+
+ b.HasIndex("DeviceId", "PersianDate")
+ .IsUnique();
+
+ b.HasIndex("DeviceId", "PersianYear", "PersianMonth");
+
+ b.ToTable("DailyReports", (string)null);
+ });
+
+ modelBuilder.Entity("GreenHome.Domain.Device", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("DeviceName")
+ .IsRequired()
+ .HasMaxLength(10)
+ .HasColumnType("nvarchar(10)");
+
+ b.Property("Location")
+ .IsRequired()
+ .HasMaxLength(250)
+ .HasColumnType("nvarchar(250)");
+
+ b.Property("NeshanLocation")
+ .IsRequired()
+ .HasMaxLength(80)
+ .HasColumnType("nvarchar(80)");
+
+ b.Property("UserId")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("Devices", (string)null);
+ });
+
+ modelBuilder.Entity("GreenHome.Domain.DevicePost", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("AuthorUserId")
+ .HasColumnType("int");
+
+ b.Property("Content")
+ .IsRequired()
+ .HasMaxLength(5000)
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("CreatedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("DeviceId")
+ .HasColumnType("int");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("datetime2");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AuthorUserId");
+
+ b.HasIndex("DeviceId", "CreatedAt");
+
+ b.ToTable("DevicePosts", (string)null);
+ });
+
+ modelBuilder.Entity("GreenHome.Domain.DevicePostImage", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("ContentType")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.Property("DevicePostId")
+ .HasColumnType("int");
+
+ b.Property("FileName")
+ .IsRequired()
+ .HasMaxLength(255)
+ .HasColumnType("nvarchar(255)");
+
+ b.Property("FilePath")
+ .IsRequired()
+ .HasMaxLength(500)
+ .HasColumnType("nvarchar(500)");
+
+ b.Property("FileSize")
+ .HasColumnType("bigint");
+
+ b.Property("UploadedAt")
+ .HasColumnType("datetime2");
+
+ b.HasKey("Id");
+
+ b.HasIndex("DevicePostId");
+
+ b.ToTable("DevicePostImages", (string)null);
+ });
+
+ modelBuilder.Entity("GreenHome.Domain.DeviceSettings", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("AreaSquareMeters")
+ .HasColumnType("decimal(18,2)");
+
+ b.Property("City")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.Property("CreatedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("DeviceId")
+ .HasColumnType("int");
+
+ b.Property("DeviceId1")
+ .HasColumnType("int");
+
+ b.Property("DevicePhoneNumber")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Latitude")
+ .HasColumnType("decimal(9,6)");
+
+ b.Property("Longitude")
+ .HasColumnType("decimal(9,6)");
+
+ b.Property("MinimumCallIntervalMinutes")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int")
+ .HasDefaultValue(60);
+
+ b.Property("MinimumSmsIntervalMinutes")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int")
+ .HasDefaultValue(15);
+
+ b.Property("ProductType")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.Property("Province")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.Property("RelayCount")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int")
+ .HasDefaultValue(4);
+
+ b.Property("SimCardType")
+ .HasColumnType("int");
+
+ b.Property("SmsRecievers")
+ .IsRequired()
+ .HasMaxLength(120)
+ .HasColumnType("nvarchar(120)");
+
+ b.Property("TokenCode")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("TokenExpiresAt")
+ .HasColumnType("datetime2");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("UploadIntervalMin")
+ .HasColumnType("int");
+
+ b.Property("VerificationCode")
+ .HasColumnType("nvarchar(max)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("DeviceId")
+ .IsUnique();
+
+ b.HasIndex("DeviceId1");
+
+ b.ToTable("DeviceSettings", (string)null);
+ });
+
+ modelBuilder.Entity("GreenHome.Domain.DeviceUser", b =>
+ {
+ b.Property("DeviceId")
+ .HasColumnType("int");
+
+ b.Property("UserId")
+ .HasColumnType("int");
+
+ b.Property("ReceiveAlerts")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bit")
+ .HasDefaultValue(true);
+
+ b.HasKey("DeviceId", "UserId");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("DeviceUsers", (string)null);
+ });
+
+ modelBuilder.Entity("GreenHome.Domain.ReportImage", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("ContentType")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.Property("Description")
+ .HasMaxLength(500)
+ .HasColumnType("nvarchar(500)");
+
+ b.Property("FileName")
+ .IsRequired()
+ .HasMaxLength(255)
+ .HasColumnType("nvarchar(255)");
+
+ b.Property("FilePath")
+ .IsRequired()
+ .HasMaxLength(500)
+ .HasColumnType("nvarchar(500)");
+
+ b.Property("FileSize")
+ .HasColumnType("bigint");
+
+ b.Property("UploadedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("UserDailyReportId")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserDailyReportId");
+
+ b.ToTable("ReportImages", (string)null);
+ });
+
+ modelBuilder.Entity("GreenHome.Domain.TelemetryRecord", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("BatteryPercent")
+ .HasColumnType("decimal(18,2)");
+
+ b.Property("BatteryVoltage")
+ .HasColumnType("decimal(18,2)");
+
+ b.Property("DeviceId")
+ .HasColumnType("int");
+
+ b.Property("GasPPM")
+ .HasColumnType("int");
+
+ b.Property("HumidityPercent")
+ .HasColumnType("decimal(18,2)");
+
+ b.Property("Lux")
+ .HasColumnType("decimal(18,2)");
+
+ b.Property("PersianDate")
+ .IsRequired()
+ .HasMaxLength(10)
+ .HasColumnType("nvarchar(10)");
+
+ b.Property("PersianMonth")
+ .HasColumnType("int");
+
+ b.Property("PersianYear")
+ .HasColumnType("int");
+
+ b.Property("Power")
+ .HasColumnType("tinyint");
+
+ b.Property("ServerTimestampUtc")
+ .HasColumnType("datetime2");
+
+ b.Property("SoilPercent")
+ .HasColumnType("decimal(18,2)");
+
+ b.Property("TemperatureC")
+ .HasColumnType("decimal(18,2)");
+
+ b.Property("TimestampUtc")
+ .HasColumnType("datetime2");
+
+ b.Property("Voltage")
+ .HasColumnType("decimal(18,2)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("DeviceId", "ServerTimestampUtc");
+
+ b.HasIndex("DeviceId", "TimestampUtc");
+
+ b.HasIndex("DeviceId", "PersianYear", "PersianMonth");
+
+ b.ToTable("Telemetry", (string)null);
+ });
+
+ modelBuilder.Entity("GreenHome.Domain.User", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("CreatedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("Family")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.Property("LastLoginAt")
+ .HasColumnType("datetime2");
+
+ b.Property("Mobile")
+ .IsRequired()
+ .HasMaxLength(11)
+ .HasColumnType("nvarchar(11)");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.Property("Role")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Mobile")
+ .IsUnique();
+
+ b.ToTable("Users", (string)null);
+ });
+
+ modelBuilder.Entity("GreenHome.Domain.UserDailyReport", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("CreatedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("DeviceId")
+ .HasColumnType("int");
+
+ b.Property("Notes")
+ .HasMaxLength(2000)
+ .HasColumnType("nvarchar(2000)");
+
+ b.Property("Observations")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Operations")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("PersianDate")
+ .IsRequired()
+ .HasMaxLength(10)
+ .HasColumnType("nvarchar(10)");
+
+ b.Property("PersianDay")
+ .HasColumnType("int");
+
+ b.Property("PersianMonth")
+ .HasColumnType("int");
+
+ b.Property("PersianYear")
+ .HasColumnType("int");
+
+ b.Property("Title")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("nvarchar(200)");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("UserId")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.HasIndex("DeviceId", "PersianDate");
+
+ b.HasIndex("DeviceId", "PersianYear", "PersianMonth");
+
+ b.ToTable("UserDailyReports", (string)null);
+ });
+
+ modelBuilder.Entity("GreenHome.Domain.VerificationCode", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("Code")
+ .IsRequired()
+ .HasMaxLength(4)
+ .HasColumnType("nvarchar(4)");
+
+ b.Property("CreatedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("ExpiresAt")
+ .HasColumnType("datetime2");
+
+ b.Property("IsUsed")
+ .HasColumnType("bit");
+
+ b.Property("Mobile")
+ .IsRequired()
+ .HasMaxLength(11)
+ .HasColumnType("nvarchar(11)");
+
+ b.Property("UsedAt")
+ .HasColumnType("datetime2");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Mobile", "Code", "IsUsed");
+
+ b.ToTable("VerificationCodes", (string)null);
+ });
+
+ modelBuilder.Entity("GreenHome.Domain.AIQuery", b =>
+ {
+ b.HasOne("GreenHome.Domain.Device", "Device")
+ .WithMany()
+ .HasForeignKey("DeviceId")
+ .OnDelete(DeleteBehavior.SetNull);
+
+ b.HasOne("GreenHome.Domain.User", "User")
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull);
+
+ b.Navigation("Device");
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("GreenHome.Domain.AlertCondition", b =>
+ {
+ b.HasOne("GreenHome.Domain.Device", "Device")
+ .WithMany()
+ .HasForeignKey("DeviceId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Device");
+ });
+
+ modelBuilder.Entity("GreenHome.Domain.AlertLog", b =>
+ {
+ b.HasOne("GreenHome.Domain.AlertCondition", "AlertCondition")
+ .WithMany()
+ .HasForeignKey("AlertConditionId")
+ .OnDelete(DeleteBehavior.SetNull);
+
+ b.HasOne("GreenHome.Domain.Device", "Device")
+ .WithMany()
+ .HasForeignKey("DeviceId")
+ .OnDelete(DeleteBehavior.Restrict)
+ .IsRequired();
+
+ b.HasOne("GreenHome.Domain.User", "User")
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Restrict)
+ .IsRequired();
+
+ b.Navigation("AlertCondition");
+
+ b.Navigation("Device");
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("GreenHome.Domain.AlertNotification", b =>
+ {
+ b.HasOne("GreenHome.Domain.AlertCondition", "AlertCondition")
+ .WithMany()
+ .HasForeignKey("AlertConditionId")
+ .OnDelete(DeleteBehavior.Restrict);
+
+ b.HasOne("GreenHome.Domain.Device", "Device")
+ .WithMany()
+ .HasForeignKey("DeviceId")
+ .OnDelete(DeleteBehavior.Restrict)
+ .IsRequired();
+
+ b.HasOne("GreenHome.Domain.User", "User")
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Restrict)
+ .IsRequired();
+
+ b.Navigation("AlertCondition");
+
+ b.Navigation("Device");
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("GreenHome.Domain.AlertRule", b =>
+ {
+ b.HasOne("GreenHome.Domain.AlertCondition", "AlertCondition")
+ .WithMany("Rules")
+ .HasForeignKey("AlertConditionId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("AlertCondition");
+ });
+
+ modelBuilder.Entity("GreenHome.Domain.Checklist", b =>
+ {
+ b.HasOne("GreenHome.Domain.User", "CreatedByUser")
+ .WithMany()
+ .HasForeignKey("CreatedByUserId")
+ .OnDelete(DeleteBehavior.Restrict)
+ .IsRequired();
+
+ b.HasOne("GreenHome.Domain.Device", "Device")
+ .WithMany()
+ .HasForeignKey("DeviceId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("CreatedByUser");
+
+ b.Navigation("Device");
+ });
+
+ modelBuilder.Entity("GreenHome.Domain.ChecklistCompletion", b =>
+ {
+ b.HasOne("GreenHome.Domain.Checklist", "Checklist")
+ .WithMany("Completions")
+ .HasForeignKey("ChecklistId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("GreenHome.Domain.User", "CompletedByUser")
+ .WithMany()
+ .HasForeignKey("CompletedByUserId")
+ .OnDelete(DeleteBehavior.Restrict)
+ .IsRequired();
+
+ b.Navigation("Checklist");
+
+ b.Navigation("CompletedByUser");
+ });
+
+ modelBuilder.Entity("GreenHome.Domain.ChecklistItem", b =>
+ {
+ b.HasOne("GreenHome.Domain.Checklist", "Checklist")
+ .WithMany("Items")
+ .HasForeignKey("ChecklistId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Checklist");
+ });
+
+ modelBuilder.Entity("GreenHome.Domain.ChecklistItemCompletion", b =>
+ {
+ b.HasOne("GreenHome.Domain.ChecklistCompletion", "ChecklistCompletion")
+ .WithMany("ItemCompletions")
+ .HasForeignKey("ChecklistCompletionId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("GreenHome.Domain.ChecklistItem", "ChecklistItem")
+ .WithMany()
+ .HasForeignKey("ChecklistItemId")
+ .OnDelete(DeleteBehavior.Restrict)
+ .IsRequired();
+
+ b.Navigation("ChecklistCompletion");
+
+ b.Navigation("ChecklistItem");
+ });
+
+ modelBuilder.Entity("GreenHome.Domain.DailyReport", b =>
+ {
+ b.HasOne("GreenHome.Domain.Device", "Device")
+ .WithMany()
+ .HasForeignKey("DeviceId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Device");
+ });
+
+ modelBuilder.Entity("GreenHome.Domain.Device", b =>
+ {
+ b.HasOne("GreenHome.Domain.User", "User")
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Restrict)
+ .IsRequired();
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("GreenHome.Domain.DevicePost", b =>
+ {
+ b.HasOne("GreenHome.Domain.User", "AuthorUser")
+ .WithMany()
+ .HasForeignKey("AuthorUserId")
+ .OnDelete(DeleteBehavior.Restrict)
+ .IsRequired();
+
+ b.HasOne("GreenHome.Domain.Device", "Device")
+ .WithMany()
+ .HasForeignKey("DeviceId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("AuthorUser");
+
+ b.Navigation("Device");
+ });
+
+ modelBuilder.Entity("GreenHome.Domain.DevicePostImage", b =>
+ {
+ b.HasOne("GreenHome.Domain.DevicePost", "DevicePost")
+ .WithMany("Images")
+ .HasForeignKey("DevicePostId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("DevicePost");
+ });
+
+ modelBuilder.Entity("GreenHome.Domain.DeviceSettings", b =>
+ {
+ b.HasOne("GreenHome.Domain.Device", "Device")
+ .WithMany()
+ .HasForeignKey("DeviceId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("GreenHome.Domain.Device", null)
+ .WithMany("DeviceSettings")
+ .HasForeignKey("DeviceId1");
+
+ b.Navigation("Device");
+ });
+
+ modelBuilder.Entity("GreenHome.Domain.DeviceUser", b =>
+ {
+ b.HasOne("GreenHome.Domain.Device", "Device")
+ .WithMany("DeviceUsers")
+ .HasForeignKey("DeviceId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("GreenHome.Domain.User", "User")
+ .WithMany("DeviceUsers")
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Device");
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("GreenHome.Domain.ReportImage", b =>
+ {
+ b.HasOne("GreenHome.Domain.UserDailyReport", "UserDailyReport")
+ .WithMany("Images")
+ .HasForeignKey("UserDailyReportId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("UserDailyReport");
+ });
+
+ modelBuilder.Entity("GreenHome.Domain.UserDailyReport", b =>
+ {
+ b.HasOne("GreenHome.Domain.Device", "Device")
+ .WithMany()
+ .HasForeignKey("DeviceId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("GreenHome.Domain.User", "User")
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Restrict)
+ .IsRequired();
+
+ b.Navigation("Device");
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("GreenHome.Domain.AlertCondition", b =>
+ {
+ b.Navigation("Rules");
+ });
+
+ modelBuilder.Entity("GreenHome.Domain.Checklist", b =>
+ {
+ b.Navigation("Completions");
+
+ b.Navigation("Items");
+ });
+
+ modelBuilder.Entity("GreenHome.Domain.ChecklistCompletion", b =>
+ {
+ b.Navigation("ItemCompletions");
+ });
+
+ modelBuilder.Entity("GreenHome.Domain.Device", b =>
+ {
+ b.Navigation("DeviceSettings");
+
+ b.Navigation("DeviceUsers");
+ });
+
+ modelBuilder.Entity("GreenHome.Domain.DevicePost", b =>
+ {
+ b.Navigation("Images");
+ });
+
+ modelBuilder.Entity("GreenHome.Domain.User", b =>
+ {
+ b.Navigation("DeviceUsers");
+ });
+
+ modelBuilder.Entity("GreenHome.Domain.UserDailyReport", b =>
+ {
+ b.Navigation("Images");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/src/GreenHome.Infrastructure/Migrations/20260428054608_AddRelayCountToDeviceSettings.cs b/src/GreenHome.Infrastructure/Migrations/20260428054608_AddRelayCountToDeviceSettings.cs
new file mode 100644
index 0000000..1a364a7
--- /dev/null
+++ b/src/GreenHome.Infrastructure/Migrations/20260428054608_AddRelayCountToDeviceSettings.cs
@@ -0,0 +1,29 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace GreenHome.Infrastructure.Migrations
+{
+ ///
+ public partial class AddRelayCountToDeviceSettings : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.AddColumn(
+ name: "RelayCount",
+ table: "DeviceSettings",
+ type: "int",
+ nullable: false,
+ defaultValue: 4);
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropColumn(
+ name: "RelayCount",
+ table: "DeviceSettings");
+ }
+ }
+}
diff --git a/src/GreenHome.Infrastructure/Migrations/GreenHomeDbContextModelSnapshot.cs b/src/GreenHome.Infrastructure/Migrations/GreenHomeDbContextModelSnapshot.cs
index 49af6f7..9e1dbb6 100644
--- a/src/GreenHome.Infrastructure/Migrations/GreenHomeDbContextModelSnapshot.cs
+++ b/src/GreenHome.Infrastructure/Migrations/GreenHomeDbContextModelSnapshot.cs
@@ -623,6 +623,11 @@ namespace GreenHome.Infrastructure.Migrations
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
+ b.Property("RelayCount")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int")
+ .HasDefaultValue(4);
+
b.Property("SimCardType")
.HasColumnType("int");