From 033a70c27c269592829309c22d8ca9b3fa2bd598 Mon Sep 17 00:00:00 2001 From: CIS Guru Date: Mon, 29 Dec 2025 18:13:01 -0600 Subject: [PATCH 1/2] Phase 2.4 notification infrastructure complete --- .../Services/NotificationService.cs | 223 + .../Core/Constants/NotificationConstants.cs | 58 + .../Core/Entities/Notification.cs | 64 + .../Core/Entities/NotificationPreferences.cs | 50 + .../Core/Interfaces/Services/IEmailService.cs | 9 + .../Core/Interfaces/Services/ISMSService.cs | 7 + .../Data/ApplicationDbContext.cs | 54 + ..._AddNotificationInfrastructure.Designer.cs | 4123 +++++++++++++++++ ...229235707_AddNotificationInfrastructure.cs | 666 +++ .../ApplicationDbContextModelSnapshot.cs | 278 +- .../Infrastructure/Services/EmailService.cs | 41 + .../Infrastructure/Services/SMSService.cs | 28 + Aquiis.SimpleStart/Program.cs | 8 + 13 files changed, 5573 insertions(+), 36 deletions(-) create mode 100644 Aquiis.SimpleStart/Application/Services/NotificationService.cs create mode 100644 Aquiis.SimpleStart/Core/Constants/NotificationConstants.cs create mode 100644 Aquiis.SimpleStart/Core/Entities/Notification.cs create mode 100644 Aquiis.SimpleStart/Core/Entities/NotificationPreferences.cs create mode 100644 Aquiis.SimpleStart/Core/Interfaces/Services/IEmailService.cs create mode 100644 Aquiis.SimpleStart/Core/Interfaces/Services/ISMSService.cs create mode 100644 Aquiis.SimpleStart/Infrastructure/Data/Migrations/20251229235707_AddNotificationInfrastructure.Designer.cs create mode 100644 Aquiis.SimpleStart/Infrastructure/Data/Migrations/20251229235707_AddNotificationInfrastructure.cs create mode 100644 Aquiis.SimpleStart/Infrastructure/Services/EmailService.cs create mode 100644 Aquiis.SimpleStart/Infrastructure/Services/SMSService.cs diff --git a/Aquiis.SimpleStart/Application/Services/NotificationService.cs b/Aquiis.SimpleStart/Application/Services/NotificationService.cs new file mode 100644 index 0000000..f823a21 --- /dev/null +++ b/Aquiis.SimpleStart/Application/Services/NotificationService.cs @@ -0,0 +1,223 @@ + +using Aquiis.SimpleStart.Core.Constants; +using Aquiis.SimpleStart.Core.Interfaces.Services; +using Aquiis.SimpleStart.Core.Services; +using Aquiis.SimpleStart.Infrastructure.Data; +using Aquiis.SimpleStart.Shared.Services; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Options; + +namespace Aquiis.SimpleStart.Application.Services; +public class NotificationService : BaseService +{ + private readonly IEmailService _emailService; + private readonly ISMSService _smsService; + private new readonly ILogger _logger; + + public NotificationService( + ApplicationDbContext context, + UserContextService userContext, + IEmailService emailService, + ISMSService smsService, + IOptions appSettings, + ILogger logger) + : base(context, logger, userContext, appSettings) + { + _emailService = emailService; + _smsService = smsService; + _logger = logger; + } + + /// + /// Create and send a notification to a user + /// + public async Task SendNotificationAsync( + string recipientUserId, + string title, + string message, + string type, + string category, + Guid? relatedEntityId = null, + string? relatedEntityType = null) + { + var organizationId = await _userContext.GetActiveOrganizationIdAsync(); + + // Get user preferences + var preferences = await GetNotificationPreferencesAsync(recipientUserId); + + var notification = new Notification + { + Id = Guid.NewGuid(), + OrganizationId = organizationId!.Value, + RecipientUserId = recipientUserId, + Title = title, + Message = message, + Type = type, + Category = category, + RelatedEntityId = relatedEntityId, + RelatedEntityType = relatedEntityType, + SentOn = DateTime.UtcNow, + IsRead = false, + SendInApp = preferences.EnableInAppNotifications, + SendEmail = preferences.EnableEmailNotifications && ShouldSendEmail(category, preferences), + SendSMS = preferences.EnableSMSNotifications && ShouldSendSMS(category, preferences) + }; + + // Save in-app notification + await CreateAsync(notification); + + // Send email if enabled + if (notification.SendEmail && !string.IsNullOrEmpty(preferences.EmailAddress)) + { + try + { + await _emailService.SendEmailAsync( + preferences.EmailAddress, + title, + message); + + notification.EmailSent = true; + notification.EmailSentOn = DateTime.UtcNow; + } + catch (Exception ex) + { + _logger.LogError(ex, $"Failed to send email notification to {recipientUserId}"); + notification.EmailError = ex.Message; + } + } + + // Send SMS if enabled + if (notification.SendSMS && !string.IsNullOrEmpty(preferences.PhoneNumber)) + { + try + { + await _smsService.SendSMSAsync( + preferences.PhoneNumber, + $"{title}: {message}"); + + notification.SMSSent = true; + notification.SMSSentOn = DateTime.UtcNow; + } + catch (Exception ex) + { + _logger.LogError(ex, $"Failed to send SMS notification to {recipientUserId}"); + notification.SMSError = ex.Message; + } + } + + await UpdateAsync(notification); + + return notification; + } + + /// + /// Mark notification as read + /// + public async Task MarkAsReadAsync(Guid notificationId) + { + var notification = await GetByIdAsync(notificationId); + if (notification == null) return; + + notification.IsRead = true; + notification.ReadOn = DateTime.UtcNow; + + await UpdateAsync(notification); + } + + /// + /// Get unread notifications for current user + /// + public async Task> GetUnreadNotificationsAsync() + { + var userId = await _userContext.GetUserIdAsync(); + var organizationId = await _userContext.GetActiveOrganizationIdAsync(); + + return await _context.Notifications + .Where(n => n.OrganizationId == organizationId + && n.RecipientUserId == userId + && !n.IsRead + && !n.IsDeleted) + .OrderByDescending(n => n.SentOn) + .Take(50) + .ToListAsync(); + } + + /// + /// Get notification history for current user + /// + public async Task> GetNotificationHistoryAsync(int count = 100) + { + var userId = await _userContext.GetUserIdAsync(); + var organizationId = await _userContext.GetActiveOrganizationIdAsync(); + + return await _context.Notifications + .Where(n => n.OrganizationId == organizationId + && n.RecipientUserId == userId + && !n.IsDeleted) + .OrderByDescending(n => n.SentOn) + .Take(count) + .ToListAsync(); + } + + /// + /// Get or create notification preferences for user + /// + private async Task GetNotificationPreferencesAsync(string userId) + { + var organizationId = await _userContext.GetActiveOrganizationIdAsync(); + + var preferences = await _context.NotificationPreferences + .FirstOrDefaultAsync(p => p.OrganizationId == organizationId + && p.UserId == userId + && !p.IsDeleted); + + if (preferences == null) + { + // Create default preferences + preferences = new NotificationPreferences + { + Id = Guid.NewGuid(), + OrganizationId = organizationId!.Value, + UserId = userId, + EnableInAppNotifications = true, + EnableEmailNotifications = true, + EnableSMSNotifications = false, + EmailLeaseExpiring = true, + EmailPaymentDue = true, + EmailPaymentReceived = true, + EmailApplicationStatusChange = true, + EmailMaintenanceUpdate = true, + EmailInspectionScheduled = true + }; + + _context.NotificationPreferences.Add(preferences); + await _context.SaveChangesAsync(); + } + + return preferences; + } + + private bool ShouldSendEmail(string category, NotificationPreferences prefs) + { + return category switch + { + NotificationConstants.Categories.Lease => prefs.EmailLeaseExpiring, + NotificationConstants.Categories.Payment => prefs.EmailPaymentDue, + NotificationConstants.Categories.Application => prefs.EmailApplicationStatusChange, + NotificationConstants.Categories.Maintenance => prefs.EmailMaintenanceUpdate, + NotificationConstants.Categories.Inspection => prefs.EmailInspectionScheduled, + _ => true + }; + } + + private bool ShouldSendSMS(string category, NotificationPreferences prefs) + { + return category switch + { + NotificationConstants.Categories.Payment => prefs.SMSPaymentDue, + NotificationConstants.Categories.Maintenance => prefs.SMSMaintenanceEmergency, + NotificationConstants.Categories.Lease => prefs.SMSLeaseExpiringUrgent, + _ => false + }; + } +} \ No newline at end of file diff --git a/Aquiis.SimpleStart/Core/Constants/NotificationConstants.cs b/Aquiis.SimpleStart/Core/Constants/NotificationConstants.cs new file mode 100644 index 0000000..1df5510 --- /dev/null +++ b/Aquiis.SimpleStart/Core/Constants/NotificationConstants.cs @@ -0,0 +1,58 @@ +public static class NotificationConstants +{ + public static class Types + { + public const string Info = "Info"; + public const string Warning = "Warning"; + public const string Error = "Error"; + public const string Success = "Success"; + } + + public static class Categories + { + public const string Lease = "Lease"; + public const string Payment = "Payment"; + public const string Maintenance = "Maintenance"; + public const string Application = "Application"; + public const string Property = "Property"; + public const string Inspection = "Inspection"; + public const string Document = "Document"; + public const string System = "System"; + } + + public static class Templates + { + // Lease notifications + public const string LeaseExpiring90Days = "lease_expiring_90"; + public const string LeaseExpiring60Days = "lease_expiring_60"; + public const string LeaseExpiring30Days = "lease_expiring_30"; + public const string LeaseActivated = "lease_activated"; + public const string LeaseTerminated = "lease_terminated"; + + // Payment notifications + public const string PaymentDueReminder = "payment_due_reminder"; + public const string PaymentReceived = "payment_received"; + public const string PaymentLate = "payment_late"; + public const string LateFeeApplied = "late_fee_applied"; + + // Maintenance notifications + public const string MaintenanceRequestCreated = "maintenance_created"; + public const string MaintenanceRequestAssigned = "maintenance_assigned"; + public const string MaintenanceRequestStarted = "maintenance_started"; + public const string MaintenanceRequestCompleted = "maintenance_completed"; + + // Application notifications + public const string ApplicationSubmitted = "application_submitted"; + public const string ApplicationUnderReview = "application_under_review"; + public const string ApplicationApproved = "application_approved"; + public const string ApplicationRejected = "application_rejected"; + + // Inspection notifications + public const string InspectionScheduled = "inspection_scheduled"; + public const string InspectionCompleted = "inspection_completed"; + + // Document notifications + public const string DocumentUploaded = "document_uploaded"; + public const string DocumentExpiring = "document_expiring"; + } +} \ No newline at end of file diff --git a/Aquiis.SimpleStart/Core/Entities/Notification.cs b/Aquiis.SimpleStart/Core/Entities/Notification.cs new file mode 100644 index 0000000..4f8bc95 --- /dev/null +++ b/Aquiis.SimpleStart/Core/Entities/Notification.cs @@ -0,0 +1,64 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Aquiis.SimpleStart.Core.Entities; +using Aquiis.SimpleStart.Core.Validation; + +public class Notification : BaseModel +{ + [RequiredGuid] + public Guid OrganizationId { get; set; } + + [Required] + [StringLength(200)] + public string Title { get; set; } = string.Empty; + + [Required] + [StringLength(2000)] + public string Message { get; set; } = string.Empty; + + [Required] + [StringLength(50)] + public string Type { get; set; } = string.Empty; // Info, Warning, Error, Success + + [Required] + [StringLength(50)] + public string Category { get; set; } = string.Empty; // Lease, Payment, Maintenance, Application + + [Required] + public string RecipientUserId { get; set; } = string.Empty; + + [Required] + public DateTime SentOn { get; set; } + + public DateTime? ReadOn { get; set; } + + public bool IsRead { get; set; } + + // Optional entity reference for "view details" link + public Guid? RelatedEntityId { get; set; } + + [StringLength(50)] + public string? RelatedEntityType { get; set; } + + // Delivery channels + public bool SendInApp { get; set; } = true; + public bool SendEmail { get; set; } + public bool SendSMS { get; set; } + + // Delivery status + public bool EmailSent { get; set; } + public DateTime? EmailSentOn { get; set; } + + public bool SMSSent { get; set; } + public DateTime? SMSSentOn { get; set; } + + [StringLength(500)] + public string? EmailError { get; set; } + + [StringLength(500)] + public string? SMSError { get; set; } + + // Navigation + [ForeignKey(nameof(OrganizationId))] + public virtual Organization? Organization { get; set; } +} \ No newline at end of file diff --git a/Aquiis.SimpleStart/Core/Entities/NotificationPreferences.cs b/Aquiis.SimpleStart/Core/Entities/NotificationPreferences.cs new file mode 100644 index 0000000..544d644 --- /dev/null +++ b/Aquiis.SimpleStart/Core/Entities/NotificationPreferences.cs @@ -0,0 +1,50 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Aquiis.SimpleStart.Core.Entities; +using Aquiis.SimpleStart.Core.Validation; + +public class NotificationPreferences : BaseModel +{ + [RequiredGuid] + public Guid OrganizationId { get; set; } + + [Required] + public string UserId { get; set; } = string.Empty; + + // In-App Notification Preferences + public bool EnableInAppNotifications { get; set; } = true; + + // Email Preferences + public bool EnableEmailNotifications { get; set; } = true; + + [StringLength(200)] + public string? EmailAddress { get; set; } + + public bool EmailLeaseExpiring { get; set; } = true; + public bool EmailPaymentDue { get; set; } = true; + public bool EmailPaymentReceived { get; set; } = true; + public bool EmailApplicationStatusChange { get; set; } = true; + public bool EmailMaintenanceUpdate { get; set; } = true; + public bool EmailInspectionScheduled { get; set; } = true; + + // SMS Preferences + public bool EnableSMSNotifications { get; set; } = false; + + [StringLength(20)] + public string? PhoneNumber { get; set; } + + public bool SMSPaymentDue { get; set; } = false; + public bool SMSMaintenanceEmergency { get; set; } = true; + public bool SMSLeaseExpiringUrgent { get; set; } = false; // 30 days or less + + // Digest Preferences + public bool EnableDailyDigest { get; set; } = false; + public TimeSpan DailyDigestTime { get; set; } = new TimeSpan(9, 0, 0); // 9 AM + + public bool EnableWeeklyDigest { get; set; } = false; + public DayOfWeek WeeklyDigestDay { get; set; } = DayOfWeek.Monday; + + // Navigation + [ForeignKey(nameof(OrganizationId))] + public virtual Organization? Organization { get; set; } +} \ No newline at end of file diff --git a/Aquiis.SimpleStart/Core/Interfaces/Services/IEmailService.cs b/Aquiis.SimpleStart/Core/Interfaces/Services/IEmailService.cs new file mode 100644 index 0000000..6337a99 --- /dev/null +++ b/Aquiis.SimpleStart/Core/Interfaces/Services/IEmailService.cs @@ -0,0 +1,9 @@ + +namespace Aquiis.SimpleStart.Core.Interfaces.Services; +public interface IEmailService +{ + Task SendEmailAsync(string to, string subject, string body); + Task SendEmailAsync(string to, string subject, string body, string? fromName = null); + Task SendTemplateEmailAsync(string to, string templateId, Dictionary templateData); + Task ValidateEmailAddressAsync(string email); +} \ No newline at end of file diff --git a/Aquiis.SimpleStart/Core/Interfaces/Services/ISMSService.cs b/Aquiis.SimpleStart/Core/Interfaces/Services/ISMSService.cs new file mode 100644 index 0000000..a25929b --- /dev/null +++ b/Aquiis.SimpleStart/Core/Interfaces/Services/ISMSService.cs @@ -0,0 +1,7 @@ + +namespace Aquiis.SimpleStart.Core.Interfaces.Services; +public interface ISMSService +{ + Task SendSMSAsync(string phoneNumber, string message); + Task ValidatePhoneNumberAsync(string phoneNumber); +} \ No newline at end of file diff --git a/Aquiis.SimpleStart/Infrastructure/Data/ApplicationDbContext.cs b/Aquiis.SimpleStart/Infrastructure/Data/ApplicationDbContext.cs index 5bc265c..75cb056 100644 --- a/Aquiis.SimpleStart/Infrastructure/Data/ApplicationDbContext.cs +++ b/Aquiis.SimpleStart/Infrastructure/Data/ApplicationDbContext.cs @@ -56,6 +56,11 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) // Workflow audit logging public DbSet WorkflowAuditLogs { get; set; } + + // Notification system + public DbSet Notifications { get; set; } + public DbSet NotificationPreferences { get; set; } + protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); @@ -531,6 +536,55 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) entity.HasIndex(e => e.PerformedBy); }); + // Configure Notification entity + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Id); + + entity.HasIndex(e => e.RecipientUserId); + entity.HasIndex(e => e.OrganizationId); + entity.HasIndex(e => e.SentOn); + entity.HasIndex(e => e.IsRead); + entity.HasIndex(e => e.Category); + + // Organization relationship + entity.HasOne(n => n.Organization) + .WithMany() + .HasForeignKey(n => n.OrganizationId) + .OnDelete(DeleteBehavior.Cascade); + + // User relationship (RecipientUserId) + entity.HasOne() + .WithMany() + .HasForeignKey(n => n.RecipientUserId) + .OnDelete(DeleteBehavior.Cascade); + }); + + // Configure NotificationPreferences entity + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Id); + + entity.HasIndex(e => e.UserId); + entity.HasIndex(e => e.OrganizationId); + + // Unique constraint: one preference record per user per organization + entity.HasIndex(e => new { e.UserId, e.OrganizationId }) + .IsUnique(); + + // Organization relationship + entity.HasOne(np => np.Organization) + .WithMany() + .HasForeignKey(np => np.OrganizationId) + .OnDelete(DeleteBehavior.Cascade); + + // User relationship + entity.HasOne() + .WithMany() + .HasForeignKey(np => np.UserId) + .OnDelete(DeleteBehavior.Cascade); + }); + // Seed System Checklist Templates SeedChecklistTemplates(modelBuilder); } diff --git a/Aquiis.SimpleStart/Infrastructure/Data/Migrations/20251229235707_AddNotificationInfrastructure.Designer.cs b/Aquiis.SimpleStart/Infrastructure/Data/Migrations/20251229235707_AddNotificationInfrastructure.Designer.cs new file mode 100644 index 0000000..40ac1c4 --- /dev/null +++ b/Aquiis.SimpleStart/Infrastructure/Data/Migrations/20251229235707_AddNotificationInfrastructure.Designer.cs @@ -0,0 +1,4123 @@ +// +using System; +using Aquiis.SimpleStart.Infrastructure.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Aquiis.SimpleStart.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20251229235707_AddNotificationInfrastructure")] + partial class AddNotificationInfrastructure + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "9.0.11"); + + modelBuilder.Entity("Aquiis.SimpleStart.Application.Services.Workflows.WorkflowAuditLog", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Action") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("EntityId") + .HasColumnType("TEXT"); + + b.Property("EntityType") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("FromStatus") + .HasColumnType("TEXT"); + + b.Property("IsDeleted") + .HasColumnType("INTEGER"); + + b.Property("LastModifiedBy") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LastModifiedOn") + .HasColumnType("TEXT"); + + b.Property("Metadata") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PerformedBy") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("PerformedOn") + .HasColumnType("TEXT"); + + b.Property("Reason") + .HasColumnType("TEXT"); + + b.Property("ToStatus") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Action"); + + b.HasIndex("EntityId"); + + b.HasIndex("EntityType"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("PerformedBy"); + + b.HasIndex("PerformedOn"); + + b.HasIndex("EntityType", "EntityId"); + + b.ToTable("WorkflowAuditLogs"); + }); + + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.ApplicationScreening", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("BackgroundCheckCompletedOn") + .HasColumnType("TEXT"); + + b.Property("BackgroundCheckNotes") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("BackgroundCheckPassed") + .HasColumnType("INTEGER"); + + b.Property("BackgroundCheckRequested") + .HasColumnType("INTEGER"); + + b.Property("BackgroundCheckRequestedOn") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("CreditCheckCompletedOn") + .HasColumnType("TEXT"); + + b.Property("CreditCheckNotes") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("CreditCheckPassed") + .HasColumnType("INTEGER"); + + b.Property("CreditCheckRequested") + .HasColumnType("INTEGER"); + + b.Property("CreditCheckRequestedOn") + .HasColumnType("TEXT"); + + b.Property("CreditScore") + .HasColumnType("INTEGER"); + + b.Property("IsDeleted") + .HasColumnType("INTEGER"); + + b.Property("LastModifiedBy") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LastModifiedOn") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("OverallResult") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("RentalApplicationId") + .HasColumnType("TEXT"); + + b.Property("ResultNotes") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("OverallResult"); + + b.HasIndex("RentalApplicationId") + .IsUnique(); + + b.ToTable("ApplicationScreenings"); + }); + + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.CalendarEvent", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Color") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("DurationMinutes") + .HasColumnType("INTEGER"); + + b.Property("EndOn") + .HasColumnType("TEXT"); + + b.Property("EventType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Icon") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("IsDeleted") + .HasColumnType("INTEGER"); + + b.Property("LastModifiedBy") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LastModifiedOn") + .HasColumnType("TEXT"); + + b.Property("Location") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PropertyId") + .HasColumnType("TEXT"); + + b.Property("SourceEntityId") + .HasColumnType("TEXT"); + + b.Property("SourceEntityType") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("StartOn") + .HasColumnType("TEXT"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EventType"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("PropertyId"); + + b.HasIndex("SourceEntityId"); + + b.HasIndex("StartOn"); + + b.HasIndex("SourceEntityType", "SourceEntityId"); + + b.ToTable("CalendarEvents"); + }); + + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.CalendarSettings", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AutoCreateEvents") + .HasColumnType("INTEGER"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("DefaultColor") + .HasColumnType("TEXT"); + + b.Property("DefaultIcon") + .HasColumnType("TEXT"); + + b.Property("DisplayOrder") + .HasColumnType("INTEGER"); + + b.Property("EntityType") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("IsDeleted") + .HasColumnType("INTEGER"); + + b.Property("LastModifiedBy") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LastModifiedOn") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("ShowOnCalendar") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("OrganizationId", "EntityType") + .IsUnique(); + + b.ToTable("CalendarSettings"); + }); + + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.Checklist", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ChecklistTemplateId") + .HasColumnType("TEXT"); + + b.Property("ChecklistType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("CompletedBy") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CompletedOn") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("DocumentId") + .HasColumnType("TEXT"); + + b.Property("GeneralNotes") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("IsDeleted") + .HasColumnType("INTEGER"); + + b.Property("LastModifiedBy") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LastModifiedOn") + .HasColumnType("TEXT"); + + b.Property("LeaseId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PropertyId") + .HasColumnType("TEXT"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ChecklistTemplateId"); + + b.HasIndex("ChecklistType"); + + b.HasIndex("CompletedOn"); + + b.HasIndex("DocumentId"); + + b.HasIndex("LeaseId"); + + b.HasIndex("PropertyId"); + + b.HasIndex("Status"); + + b.ToTable("Checklists"); + }); + + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.ChecklistItem", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CategorySection") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ChecklistId") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("IsChecked") + .HasColumnType("INTEGER"); + + b.Property("IsDeleted") + .HasColumnType("INTEGER"); + + b.Property("ItemOrder") + .HasColumnType("INTEGER"); + + b.Property("ItemText") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("LastModifiedBy") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LastModifiedOn") + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PhotoUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("RequiresValue") + .HasColumnType("INTEGER"); + + b.Property("SectionOrder") + .HasColumnType("INTEGER"); + + b.Property("Value") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ChecklistId"); + + b.ToTable("ChecklistItems"); + }); + + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.ChecklistTemplate", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Category") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("IsDeleted") + .HasColumnType("INTEGER"); + + b.Property("IsSystemTemplate") + .HasColumnType("INTEGER"); + + b.Property("LastModifiedBy") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LastModifiedOn") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Category"); + + b.HasIndex("OrganizationId"); + + b.ToTable("ChecklistTemplates"); + + b.HasData( + new + { + Id = new Guid("00000000-0000-0000-0001-000000000001"), + Category = "Tour", + CreatedBy = "", + CreatedOn = new DateTime(2025, 11, 30, 0, 0, 0, 0, DateTimeKind.Utc), + Description = "Standard property showing checklist", + IsDeleted = false, + IsSystemTemplate = true, + Name = "Property Tour", + OrganizationId = new Guid("00000000-0000-0000-0000-000000000000") + }, + new + { + Id = new Guid("00000000-0000-0000-0001-000000000002"), + Category = "MoveIn", + CreatedBy = "", + CreatedOn = new DateTime(2025, 11, 30, 0, 0, 0, 0, DateTimeKind.Utc), + Description = "Move-in inspection checklist", + IsDeleted = false, + IsSystemTemplate = true, + Name = "Move-In", + OrganizationId = new Guid("00000000-0000-0000-0000-000000000000") + }, + new + { + Id = new Guid("00000000-0000-0000-0001-000000000003"), + Category = "MoveOut", + CreatedBy = "", + CreatedOn = new DateTime(2025, 11, 30, 0, 0, 0, 0, DateTimeKind.Utc), + Description = "Move-out inspection checklist", + IsDeleted = false, + IsSystemTemplate = true, + Name = "Move-Out", + OrganizationId = new Guid("00000000-0000-0000-0000-000000000000") + }, + new + { + Id = new Guid("00000000-0000-0000-0001-000000000004"), + Category = "Tour", + CreatedBy = "", + CreatedOn = new DateTime(2025, 11, 30, 0, 0, 0, 0, DateTimeKind.Utc), + Description = "Open house event checklist", + IsDeleted = false, + IsSystemTemplate = true, + Name = "Open House", + OrganizationId = new Guid("00000000-0000-0000-0000-000000000000") + }); + }); + + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.ChecklistTemplateItem", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AllowsNotes") + .HasColumnType("INTEGER"); + + b.Property("CategorySection") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ChecklistTemplateId") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("IsDeleted") + .HasColumnType("INTEGER"); + + b.Property("IsRequired") + .HasColumnType("INTEGER"); + + b.Property("ItemOrder") + .HasColumnType("INTEGER"); + + b.Property("ItemText") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("LastModifiedBy") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LastModifiedOn") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RequiresValue") + .HasColumnType("INTEGER"); + + b.Property("SectionOrder") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ChecklistTemplateId"); + + b.ToTable("ChecklistTemplateItems"); + + b.HasData( + new + { + Id = new Guid("00000000-0000-0000-0002-000000000001"), + AllowsNotes = true, + CategorySection = "Arrival & Introduction", + ChecklistTemplateId = new Guid("00000000-0000-0000-0001-000000000001"), + CreatedBy = "", + CreatedOn = new DateTime(2025, 11, 30, 0, 0, 0, 0, DateTimeKind.Utc), + IsDeleted = false, + IsRequired = true, + ItemOrder = 1, + ItemText = "Greeted prospect and verified appointment", + OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), + RequiresValue = false, + SectionOrder = 1 + }, + new + { + Id = new Guid("00000000-0000-0000-0002-000000000002"), + AllowsNotes = true, + CategorySection = "Arrival & Introduction", + ChecklistTemplateId = new Guid("00000000-0000-0000-0001-000000000001"), + CreatedBy = "", + CreatedOn = new DateTime(2025, 11, 30, 0, 0, 0, 0, DateTimeKind.Utc), + IsDeleted = false, + IsRequired = true, + ItemOrder = 2, + ItemText = "Reviewed property exterior and curb appeal", + OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), + RequiresValue = false, + SectionOrder = 1 + }, + new + { + Id = new Guid("00000000-0000-0000-0002-000000000003"), + AllowsNotes = true, + CategorySection = "Arrival & Introduction", + ChecklistTemplateId = new Guid("00000000-0000-0000-0001-000000000001"), + CreatedBy = "", + CreatedOn = new DateTime(2025, 11, 30, 0, 0, 0, 0, DateTimeKind.Utc), + IsDeleted = false, + IsRequired = true, + ItemOrder = 3, + ItemText = "Showed parking area/garage", + OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), + RequiresValue = false, + SectionOrder = 1 + }, + new + { + Id = new Guid("00000000-0000-0000-0002-000000000004"), + AllowsNotes = true, + CategorySection = "Interior Tour", + ChecklistTemplateId = new Guid("00000000-0000-0000-0001-000000000001"), + CreatedBy = "", + CreatedOn = new DateTime(2025, 11, 30, 0, 0, 0, 0, DateTimeKind.Utc), + IsDeleted = false, + IsRequired = true, + ItemOrder = 4, + ItemText = "Toured living room/common areas", + OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), + RequiresValue = false, + SectionOrder = 2 + }, + new + { + Id = new Guid("00000000-0000-0000-0002-000000000005"), + AllowsNotes = true, + CategorySection = "Interior Tour", + ChecklistTemplateId = new Guid("00000000-0000-0000-0001-000000000001"), + CreatedBy = "", + CreatedOn = new DateTime(2025, 11, 30, 0, 0, 0, 0, DateTimeKind.Utc), + IsDeleted = false, + IsRequired = true, + ItemOrder = 5, + ItemText = "Showed all bedrooms", + OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), + RequiresValue = false, + SectionOrder = 2 + }, + new + { + Id = new Guid("00000000-0000-0000-0002-000000000006"), + AllowsNotes = true, + CategorySection = "Interior Tour", + ChecklistTemplateId = new Guid("00000000-0000-0000-0001-000000000001"), + CreatedBy = "", + CreatedOn = new DateTime(2025, 11, 30, 0, 0, 0, 0, DateTimeKind.Utc), + IsDeleted = false, + IsRequired = true, + ItemOrder = 6, + ItemText = "Showed all bathrooms", + OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), + RequiresValue = false, + SectionOrder = 2 + }, + new + { + Id = new Guid("00000000-0000-0000-0002-000000000007"), + AllowsNotes = true, + CategorySection = "Kitchen & Appliances", + ChecklistTemplateId = new Guid("00000000-0000-0000-0001-000000000001"), + CreatedBy = "", + CreatedOn = new DateTime(2025, 11, 30, 0, 0, 0, 0, DateTimeKind.Utc), + IsDeleted = false, + IsRequired = true, + ItemOrder = 7, + ItemText = "Toured kitchen and demonstrated appliances", + OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), + RequiresValue = false, + SectionOrder = 3 + }, + new + { + Id = new Guid("00000000-0000-0000-0002-000000000008"), + AllowsNotes = true, + CategorySection = "Kitchen & Appliances", + ChecklistTemplateId = new Guid("00000000-0000-0000-0001-000000000001"), + CreatedBy = "", + CreatedOn = new DateTime(2025, 11, 30, 0, 0, 0, 0, DateTimeKind.Utc), + IsDeleted = false, + IsRequired = true, + ItemOrder = 8, + ItemText = "Explained which appliances are included", + OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), + RequiresValue = false, + SectionOrder = 3 + }, + new + { + Id = new Guid("00000000-0000-0000-0002-000000000009"), + AllowsNotes = true, + CategorySection = "Utilities & Systems", + ChecklistTemplateId = new Guid("00000000-0000-0000-0001-000000000001"), + CreatedBy = "", + CreatedOn = new DateTime(2025, 11, 30, 0, 0, 0, 0, DateTimeKind.Utc), + IsDeleted = false, + IsRequired = true, + ItemOrder = 9, + ItemText = "Explained HVAC system and thermostat controls", + OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), + RequiresValue = false, + SectionOrder = 4 + }, + new + { + Id = new Guid("00000000-0000-0000-0002-000000000010"), + AllowsNotes = true, + CategorySection = "Utilities & Systems", + ChecklistTemplateId = new Guid("00000000-0000-0000-0001-000000000001"), + CreatedBy = "", + CreatedOn = new DateTime(2025, 11, 30, 0, 0, 0, 0, DateTimeKind.Utc), + IsDeleted = false, + IsRequired = true, + ItemOrder = 10, + ItemText = "Reviewed utility responsibilities (tenant vs landlord)", + OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), + RequiresValue = false, + SectionOrder = 4 + }, + new + { + Id = new Guid("00000000-0000-0000-0002-000000000011"), + AllowsNotes = true, + CategorySection = "Utilities & Systems", + ChecklistTemplateId = new Guid("00000000-0000-0000-0001-000000000001"), + CreatedBy = "", + CreatedOn = new DateTime(2025, 11, 30, 0, 0, 0, 0, DateTimeKind.Utc), + IsDeleted = false, + IsRequired = true, + ItemOrder = 11, + ItemText = "Showed water heater location", + OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), + RequiresValue = false, + SectionOrder = 4 + }, + new + { + Id = new Guid("00000000-0000-0000-0002-000000000012"), + AllowsNotes = true, + CategorySection = "Storage & Amenities", + ChecklistTemplateId = new Guid("00000000-0000-0000-0001-000000000001"), + CreatedBy = "", + CreatedOn = new DateTime(2025, 11, 30, 0, 0, 0, 0, DateTimeKind.Utc), + IsDeleted = false, + IsRequired = true, + ItemOrder = 12, + ItemText = "Showed storage areas (closets, attic, basement)", + OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), + RequiresValue = false, + SectionOrder = 5 + }, + new + { + Id = new Guid("00000000-0000-0000-0002-000000000013"), + AllowsNotes = true, + CategorySection = "Storage & Amenities", + ChecklistTemplateId = new Guid("00000000-0000-0000-0001-000000000001"), + CreatedBy = "", + CreatedOn = new DateTime(2025, 11, 30, 0, 0, 0, 0, DateTimeKind.Utc), + IsDeleted = false, + IsRequired = true, + ItemOrder = 13, + ItemText = "Showed laundry facilities", + OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), + RequiresValue = false, + SectionOrder = 5 + }, + new + { + Id = new Guid("00000000-0000-0000-0002-000000000014"), + AllowsNotes = true, + CategorySection = "Storage & Amenities", + ChecklistTemplateId = new Guid("00000000-0000-0000-0001-000000000001"), + CreatedBy = "", + CreatedOn = new DateTime(2025, 11, 30, 0, 0, 0, 0, DateTimeKind.Utc), + IsDeleted = false, + IsRequired = true, + ItemOrder = 14, + ItemText = "Showed outdoor space (yard, patio, balcony)", + OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), + RequiresValue = false, + SectionOrder = 5 + }, + new + { + Id = new Guid("00000000-0000-0000-0002-000000000015"), + AllowsNotes = true, + CategorySection = "Lease Terms", + ChecklistTemplateId = new Guid("00000000-0000-0000-0001-000000000001"), + CreatedBy = "", + CreatedOn = new DateTime(2025, 11, 30, 0, 0, 0, 0, DateTimeKind.Utc), + IsDeleted = false, + IsRequired = true, + ItemOrder = 15, + ItemText = "Discussed monthly rent amount", + OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), + RequiresValue = false, + SectionOrder = 6 + }, + new + { + Id = new Guid("00000000-0000-0000-0002-000000000016"), + AllowsNotes = true, + CategorySection = "Lease Terms", + ChecklistTemplateId = new Guid("00000000-0000-0000-0001-000000000001"), + CreatedBy = "", + CreatedOn = new DateTime(2025, 11, 30, 0, 0, 0, 0, DateTimeKind.Utc), + IsDeleted = false, + IsRequired = true, + ItemOrder = 16, + ItemText = "Explained security deposit and move-in costs", + OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), + RequiresValue = false, + SectionOrder = 6 + }, + new + { + Id = new Guid("00000000-0000-0000-0002-000000000017"), + AllowsNotes = true, + CategorySection = "Lease Terms", + ChecklistTemplateId = new Guid("00000000-0000-0000-0001-000000000001"), + CreatedBy = "", + CreatedOn = new DateTime(2025, 11, 30, 0, 0, 0, 0, DateTimeKind.Utc), + IsDeleted = false, + IsRequired = true, + ItemOrder = 17, + ItemText = "Reviewed lease term length and start date", + OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), + RequiresValue = false, + SectionOrder = 6 + }, + new + { + Id = new Guid("00000000-0000-0000-0002-000000000018"), + AllowsNotes = true, + CategorySection = "Lease Terms", + ChecklistTemplateId = new Guid("00000000-0000-0000-0001-000000000001"), + CreatedBy = "", + CreatedOn = new DateTime(2025, 11, 30, 0, 0, 0, 0, DateTimeKind.Utc), + IsDeleted = false, + IsRequired = true, + ItemOrder = 18, + ItemText = "Explained pet policy", + OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), + RequiresValue = false, + SectionOrder = 6 + }, + new + { + Id = new Guid("00000000-0000-0000-0002-000000000019"), + AllowsNotes = true, + CategorySection = "Next Steps", + ChecklistTemplateId = new Guid("00000000-0000-0000-0001-000000000001"), + CreatedBy = "", + CreatedOn = new DateTime(2025, 11, 30, 0, 0, 0, 0, DateTimeKind.Utc), + IsDeleted = false, + IsRequired = true, + ItemOrder = 19, + ItemText = "Explained application process and requirements", + OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), + RequiresValue = false, + SectionOrder = 7 + }, + new + { + Id = new Guid("00000000-0000-0000-0002-000000000020"), + AllowsNotes = true, + CategorySection = "Next Steps", + ChecklistTemplateId = new Guid("00000000-0000-0000-0001-000000000001"), + CreatedBy = "", + CreatedOn = new DateTime(2025, 11, 30, 0, 0, 0, 0, DateTimeKind.Utc), + IsDeleted = false, + IsRequired = true, + ItemOrder = 20, + ItemText = "Reviewed screening process (background, credit check)", + OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), + RequiresValue = false, + SectionOrder = 7 + }, + new + { + Id = new Guid("00000000-0000-0000-0002-000000000021"), + AllowsNotes = true, + CategorySection = "Next Steps", + ChecklistTemplateId = new Guid("00000000-0000-0000-0001-000000000001"), + CreatedBy = "", + CreatedOn = new DateTime(2025, 11, 30, 0, 0, 0, 0, DateTimeKind.Utc), + IsDeleted = false, + IsRequired = true, + ItemOrder = 21, + ItemText = "Answered all prospect questions", + OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), + RequiresValue = false, + SectionOrder = 7 + }, + new + { + Id = new Guid("00000000-0000-0000-0002-000000000022"), + AllowsNotes = true, + CategorySection = "Assessment", + ChecklistTemplateId = new Guid("00000000-0000-0000-0001-000000000001"), + CreatedBy = "", + CreatedOn = new DateTime(2025, 11, 30, 0, 0, 0, 0, DateTimeKind.Utc), + IsDeleted = false, + IsRequired = true, + ItemOrder = 22, + ItemText = "Prospect Interest Level", + OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), + RequiresValue = true, + SectionOrder = 8 + }, + new + { + Id = new Guid("00000000-0000-0000-0002-000000000023"), + AllowsNotes = true, + CategorySection = "Assessment", + ChecklistTemplateId = new Guid("00000000-0000-0000-0001-000000000001"), + CreatedBy = "", + CreatedOn = new DateTime(2025, 11, 30, 0, 0, 0, 0, DateTimeKind.Utc), + IsDeleted = false, + IsRequired = true, + ItemOrder = 23, + ItemText = "Overall showing feedback and notes", + OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), + RequiresValue = false, + SectionOrder = 8 + }, + new + { + Id = new Guid("00000000-0000-0000-0002-000000000024"), + AllowsNotes = true, + CategorySection = "General", + ChecklistTemplateId = new Guid("00000000-0000-0000-0001-000000000002"), + CreatedBy = "", + CreatedOn = new DateTime(2025, 11, 30, 0, 0, 0, 0, DateTimeKind.Utc), + IsDeleted = false, + IsRequired = true, + ItemOrder = 1, + ItemText = "Document property condition", + OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), + RequiresValue = false, + SectionOrder = 1 + }, + new + { + Id = new Guid("00000000-0000-0000-0002-000000000025"), + AllowsNotes = true, + CategorySection = "General", + ChecklistTemplateId = new Guid("00000000-0000-0000-0001-000000000002"), + CreatedBy = "", + CreatedOn = new DateTime(2025, 11, 30, 0, 0, 0, 0, DateTimeKind.Utc), + IsDeleted = false, + IsRequired = true, + ItemOrder = 2, + ItemText = "Collect keys and access codes", + OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), + RequiresValue = false, + SectionOrder = 1 + }, + new + { + Id = new Guid("00000000-0000-0000-0002-000000000026"), + AllowsNotes = true, + CategorySection = "General", + ChecklistTemplateId = new Guid("00000000-0000-0000-0001-000000000002"), + CreatedBy = "", + CreatedOn = new DateTime(2025, 11, 30, 0, 0, 0, 0, DateTimeKind.Utc), + IsDeleted = false, + IsRequired = true, + ItemOrder = 3, + ItemText = "Review lease terms with tenant", + OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), + RequiresValue = false, + SectionOrder = 1 + }, + new + { + Id = new Guid("00000000-0000-0000-0002-000000000027"), + AllowsNotes = true, + CategorySection = "General", + ChecklistTemplateId = new Guid("00000000-0000-0000-0001-000000000003"), + CreatedBy = "", + CreatedOn = new DateTime(2025, 11, 30, 0, 0, 0, 0, DateTimeKind.Utc), + IsDeleted = false, + IsRequired = true, + ItemOrder = 1, + ItemText = "Inspect property condition", + OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), + RequiresValue = false, + SectionOrder = 1 + }, + new + { + Id = new Guid("00000000-0000-0000-0002-000000000028"), + AllowsNotes = true, + CategorySection = "General", + ChecklistTemplateId = new Guid("00000000-0000-0000-0001-000000000003"), + CreatedBy = "", + CreatedOn = new DateTime(2025, 11, 30, 0, 0, 0, 0, DateTimeKind.Utc), + IsDeleted = false, + IsRequired = true, + ItemOrder = 2, + ItemText = "Collect all keys and access devices", + OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), + RequiresValue = false, + SectionOrder = 1 + }, + new + { + Id = new Guid("00000000-0000-0000-0002-000000000029"), + AllowsNotes = true, + CategorySection = "General", + ChecklistTemplateId = new Guid("00000000-0000-0000-0001-000000000003"), + CreatedBy = "", + CreatedOn = new DateTime(2025, 11, 30, 0, 0, 0, 0, DateTimeKind.Utc), + IsDeleted = false, + IsRequired = true, + ItemOrder = 3, + ItemText = "Document damages and needed repairs", + OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), + RequiresValue = false, + SectionOrder = 1 + }, + new + { + Id = new Guid("00000000-0000-0000-0002-000000000030"), + AllowsNotes = true, + CategorySection = "Preparation", + ChecklistTemplateId = new Guid("00000000-0000-0000-0001-000000000004"), + CreatedBy = "", + CreatedOn = new DateTime(2025, 11, 30, 0, 0, 0, 0, DateTimeKind.Utc), + IsDeleted = false, + IsRequired = true, + ItemOrder = 1, + ItemText = "Set up signage and directional markers", + OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), + RequiresValue = false, + SectionOrder = 1 + }, + new + { + Id = new Guid("00000000-0000-0000-0002-000000000031"), + AllowsNotes = true, + CategorySection = "Preparation", + ChecklistTemplateId = new Guid("00000000-0000-0000-0001-000000000004"), + CreatedBy = "", + CreatedOn = new DateTime(2025, 11, 30, 0, 0, 0, 0, DateTimeKind.Utc), + IsDeleted = false, + IsRequired = true, + ItemOrder = 2, + ItemText = "Prepare information packets", + OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), + RequiresValue = false, + SectionOrder = 1 + }, + new + { + Id = new Guid("00000000-0000-0000-0002-000000000032"), + AllowsNotes = true, + CategorySection = "Preparation", + ChecklistTemplateId = new Guid("00000000-0000-0000-0001-000000000004"), + CreatedBy = "", + CreatedOn = new DateTime(2025, 11, 30, 0, 0, 0, 0, DateTimeKind.Utc), + IsDeleted = false, + IsRequired = true, + ItemOrder = 3, + ItemText = "Set up visitor sign-in sheet", + OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), + RequiresValue = false, + SectionOrder = 1 + }); + }); + + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.Document", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ContentType") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("DocumentType") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("FileData") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("FileExtension") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("FilePath") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("FileSize") + .HasColumnType("INTEGER"); + + b.Property("FileType") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("InvoiceId") + .HasColumnType("TEXT"); + + b.Property("IsDeleted") + .HasColumnType("INTEGER"); + + b.Property("LastModifiedBy") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LastModifiedOn") + .HasColumnType("TEXT"); + + b.Property("LeaseId") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("PaymentId") + .HasColumnType("TEXT"); + + b.Property("PropertyId") + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("InvoiceId"); + + b.HasIndex("LeaseId"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("PaymentId"); + + b.HasIndex("PropertyId"); + + b.HasIndex("TenantId"); + + b.ToTable("Documents"); + }); + + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.Inspection", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ActionItemsRequired") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("BathroomSinkGood") + .HasColumnType("INTEGER"); + + b.Property("BathroomSinkNotes") + .HasColumnType("TEXT"); + + b.Property("BathroomToiletGood") + .HasColumnType("INTEGER"); + + b.Property("BathroomToiletNotes") + .HasColumnType("TEXT"); + + b.Property("BathroomTubShowerGood") + .HasColumnType("INTEGER"); + + b.Property("BathroomTubShowerNotes") + .HasColumnType("TEXT"); + + b.Property("BathroomVentilationGood") + .HasColumnType("INTEGER"); + + b.Property("BathroomVentilationNotes") + .HasColumnType("TEXT"); + + b.Property("CalendarEventId") + .HasColumnType("TEXT"); + + b.Property("CarbonMonoxideDetectorsGood") + .HasColumnType("INTEGER"); + + b.Property("CarbonMonoxideDetectorsNotes") + .HasColumnType("TEXT"); + + b.Property("CompletedOn") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("DocumentId") + .HasColumnType("TEXT"); + + b.Property("ElectricalSystemGood") + .HasColumnType("INTEGER"); + + b.Property("ElectricalSystemNotes") + .HasColumnType("TEXT"); + + b.Property("ExteriorDoorsGood") + .HasColumnType("INTEGER"); + + b.Property("ExteriorDoorsNotes") + .HasColumnType("TEXT"); + + b.Property("ExteriorFoundationGood") + .HasColumnType("INTEGER"); + + b.Property("ExteriorFoundationNotes") + .HasColumnType("TEXT"); + + b.Property("ExteriorGuttersGood") + .HasColumnType("INTEGER"); + + b.Property("ExteriorGuttersNotes") + .HasColumnType("TEXT"); + + b.Property("ExteriorRoofGood") + .HasColumnType("INTEGER"); + + b.Property("ExteriorRoofNotes") + .HasColumnType("TEXT"); + + b.Property("ExteriorSidingGood") + .HasColumnType("INTEGER"); + + b.Property("ExteriorSidingNotes") + .HasColumnType("TEXT"); + + b.Property("ExteriorWindowsGood") + .HasColumnType("INTEGER"); + + b.Property("ExteriorWindowsNotes") + .HasColumnType("TEXT"); + + b.Property("GeneralNotes") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("HvacSystemGood") + .HasColumnType("INTEGER"); + + b.Property("HvacSystemNotes") + .HasColumnType("TEXT"); + + b.Property("InspectedBy") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("InspectionType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("InteriorCeilingsGood") + .HasColumnType("INTEGER"); + + b.Property("InteriorCeilingsNotes") + .HasColumnType("TEXT"); + + b.Property("InteriorDoorsGood") + .HasColumnType("INTEGER"); + + b.Property("InteriorDoorsNotes") + .HasColumnType("TEXT"); + + b.Property("InteriorFloorsGood") + .HasColumnType("INTEGER"); + + b.Property("InteriorFloorsNotes") + .HasColumnType("TEXT"); + + b.Property("InteriorWallsGood") + .HasColumnType("INTEGER"); + + b.Property("InteriorWallsNotes") + .HasColumnType("TEXT"); + + b.Property("InteriorWindowsGood") + .HasColumnType("INTEGER"); + + b.Property("InteriorWindowsNotes") + .HasColumnType("TEXT"); + + b.Property("IsDeleted") + .HasColumnType("INTEGER"); + + b.Property("KitchenAppliancesGood") + .HasColumnType("INTEGER"); + + b.Property("KitchenAppliancesNotes") + .HasColumnType("TEXT"); + + b.Property("KitchenCabinetsGood") + .HasColumnType("INTEGER"); + + b.Property("KitchenCabinetsNotes") + .HasColumnType("TEXT"); + + b.Property("KitchenCountersGood") + .HasColumnType("INTEGER"); + + b.Property("KitchenCountersNotes") + .HasColumnType("TEXT"); + + b.Property("KitchenSinkPlumbingGood") + .HasColumnType("INTEGER"); + + b.Property("KitchenSinkPlumbingNotes") + .HasColumnType("TEXT"); + + b.Property("LandscapingGood") + .HasColumnType("INTEGER"); + + b.Property("LandscapingNotes") + .HasColumnType("TEXT"); + + b.Property("LastModifiedBy") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LastModifiedOn") + .HasColumnType("TEXT"); + + b.Property("LeaseId") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("OverallCondition") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("PlumbingSystemGood") + .HasColumnType("INTEGER"); + + b.Property("PlumbingSystemNotes") + .HasColumnType("TEXT"); + + b.Property("PropertyId") + .HasColumnType("TEXT"); + + b.Property("SmokeDetectorsGood") + .HasColumnType("INTEGER"); + + b.Property("SmokeDetectorsNotes") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CompletedOn"); + + b.HasIndex("DocumentId"); + + b.HasIndex("LeaseId"); + + b.HasIndex("PropertyId"); + + b.ToTable("Inspections"); + }); + + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.Invoice", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("AmountPaid") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("DocumentId") + .HasColumnType("TEXT"); + + b.Property("DueOn") + .HasColumnType("TEXT"); + + b.Property("InvoiceNumber") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("InvoicedOn") + .HasColumnType("TEXT"); + + b.Property("IsDeleted") + .HasColumnType("INTEGER"); + + b.Property("LastModifiedBy") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LastModifiedOn") + .HasColumnType("TEXT"); + + b.Property("LateFeeAmount") + .HasColumnType("decimal(18,2)"); + + b.Property("LateFeeApplied") + .HasColumnType("INTEGER"); + + b.Property("LateFeeAppliedOn") + .HasColumnType("TEXT"); + + b.Property("LeaseId") + .HasColumnType("TEXT"); + + b.Property("Notes") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("PaidOn") + .HasColumnType("TEXT"); + + b.Property("ReminderSent") + .HasColumnType("INTEGER"); + + b.Property("ReminderSentOn") + .HasColumnType("TEXT"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DocumentId"); + + b.HasIndex("InvoiceNumber") + .IsUnique(); + + b.HasIndex("LeaseId"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Invoices"); + }); + + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.Lease", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ActualMoveOutDate") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("DeclinedOn") + .HasColumnType("TEXT"); + + b.Property("DocumentId") + .HasColumnType("TEXT"); + + b.Property("EndDate") + .HasColumnType("TEXT"); + + b.Property("ExpectedMoveOutDate") + .HasColumnType("TEXT"); + + b.Property("ExpiresOn") + .HasColumnType("TEXT"); + + b.Property("IsDeleted") + .HasColumnType("INTEGER"); + + b.Property("LastModifiedBy") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LastModifiedOn") + .HasColumnType("TEXT"); + + b.Property("LeaseOfferId") + .HasColumnType("TEXT"); + + b.Property("MonthlyRent") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("Notes") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("OfferedOn") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PreviousLeaseId") + .HasColumnType("TEXT"); + + b.Property("PropertyId") + .HasColumnType("TEXT"); + + b.Property("ProposedRenewalRent") + .HasColumnType("decimal(18,2)"); + + b.Property("RenewalNotes") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("RenewalNotificationSent") + .HasColumnType("INTEGER"); + + b.Property("RenewalNotificationSentOn") + .HasColumnType("TEXT"); + + b.Property("RenewalNumber") + .HasColumnType("INTEGER"); + + b.Property("RenewalOfferedOn") + .HasColumnType("TEXT"); + + b.Property("RenewalReminderSentOn") + .HasColumnType("TEXT"); + + b.Property("RenewalResponseOn") + .HasColumnType("TEXT"); + + b.Property("RenewalStatus") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("SecurityDeposit") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("SignedOn") + .HasColumnType("TEXT"); + + b.Property("StartDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("TerminationNoticedOn") + .HasColumnType("TEXT"); + + b.Property("TerminationReason") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("Terms") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DocumentId"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("PropertyId"); + + b.HasIndex("TenantId"); + + b.ToTable("Leases"); + }); + + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.LeaseOffer", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConvertedLeaseId") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("EndDate") + .HasColumnType("TEXT"); + + b.Property("ExpiresOn") + .HasColumnType("TEXT"); + + b.Property("IsDeleted") + .HasColumnType("INTEGER"); + + b.Property("LastModifiedBy") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LastModifiedOn") + .HasColumnType("TEXT"); + + b.Property("MonthlyRent") + .HasColumnType("decimal(18,2)"); + + b.Property("Notes") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("OfferedOn") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("PropertyId") + .HasColumnType("TEXT"); + + b.Property("ProspectiveTenantId") + .HasColumnType("TEXT"); + + b.Property("RentalApplicationId") + .HasColumnType("TEXT"); + + b.Property("RespondedOn") + .HasColumnType("TEXT"); + + b.Property("ResponseNotes") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("SecurityDeposit") + .HasColumnType("decimal(18,2)"); + + b.Property("StartDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Terms") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PropertyId"); + + b.HasIndex("ProspectiveTenantId"); + + b.HasIndex("RentalApplicationId"); + + b.ToTable("LeaseOffers"); + }); + + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.MaintenanceRequest", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ActualCost") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("AssignedTo") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CalendarEventId") + .HasColumnType("TEXT"); + + b.Property("CompletedOn") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("EstimatedCost") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("IsDeleted") + .HasColumnType("INTEGER"); + + b.Property("LastModifiedBy") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LastModifiedOn") + .HasColumnType("TEXT"); + + b.Property("LeaseId") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Priority") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("PropertyId") + .HasColumnType("TEXT"); + + b.Property("RequestType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("RequestedBy") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("RequestedByEmail") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("RequestedByPhone") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("RequestedOn") + .HasColumnType("TEXT"); + + b.Property("ResolutionNotes") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("ScheduledOn") + .HasColumnType("TEXT"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("LeaseId"); + + b.HasIndex("Priority"); + + b.HasIndex("PropertyId"); + + b.HasIndex("RequestedOn"); + + b.HasIndex("Status"); + + b.ToTable("MaintenanceRequests"); + }); + + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.Note", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(5000) + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("EntityId") + .HasColumnType("TEXT"); + + b.Property("EntityType") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("IsDeleted") + .HasColumnType("INTEGER"); + + b.Property("LastModifiedBy") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LastModifiedOn") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("UserFullName") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.ToTable("Notes"); + }); + + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.Organization", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("IsDeleted") + .HasColumnType("INTEGER"); + + b.Property("LastModifiedBy") + .HasColumnType("TEXT"); + + b.Property("LastModifiedOn") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("OwnerId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("State") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("IsActive"); + + b.HasIndex("OwnerId"); + + b.ToTable("Organizations"); + }); + + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.OrganizationSettings", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AllowTenantDividendChoice") + .HasColumnType("INTEGER"); + + b.Property("ApplicationExpirationDays") + .HasColumnType("INTEGER"); + + b.Property("ApplicationFeeEnabled") + .HasColumnType("INTEGER"); + + b.Property("AutoCalculateSecurityDeposit") + .HasColumnType("INTEGER"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("DefaultApplicationFee") + .HasPrecision(18, 2) + .HasColumnType("TEXT"); + + b.Property("DefaultDividendPaymentMethod") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("DividendDistributionMonth") + .HasColumnType("INTEGER"); + + b.Property("IsDeleted") + .HasColumnType("INTEGER"); + + b.Property("LastModifiedBy") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LastModifiedOn") + .HasColumnType("TEXT"); + + b.Property("LateFeeAutoApply") + .HasColumnType("INTEGER"); + + b.Property("LateFeeEnabled") + .HasColumnType("INTEGER"); + + b.Property("LateFeeGracePeriodDays") + .HasColumnType("INTEGER"); + + b.Property("LateFeePercentage") + .HasPrecision(5, 4) + .HasColumnType("TEXT"); + + b.Property("MaxLateFeeAmount") + .HasPrecision(18, 2) + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("OrganizationSharePercentage") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.Property("PaymentReminderDaysBefore") + .HasColumnType("INTEGER"); + + b.Property("PaymentReminderEnabled") + .HasColumnType("INTEGER"); + + b.Property("RefundProcessingDays") + .HasColumnType("INTEGER"); + + b.Property("SecurityDepositInvestmentEnabled") + .HasColumnType("INTEGER"); + + b.Property("SecurityDepositMultiplier") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("TourNoShowGracePeriodHours") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .IsUnique(); + + b.ToTable("OrganizationSettings"); + }); + + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.Payment", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("DocumentId") + .HasColumnType("TEXT"); + + b.Property("InvoiceId") + .HasColumnType("TEXT"); + + b.Property("IsDeleted") + .HasColumnType("INTEGER"); + + b.Property("LastModifiedBy") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LastModifiedOn") + .HasColumnType("TEXT"); + + b.Property("Notes") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("PaidOn") + .HasColumnType("TEXT"); + + b.Property("PaymentMethod") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DocumentId"); + + b.HasIndex("InvoiceId"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Payments"); + }); + + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.Property", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Address") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Bathrooms") + .HasMaxLength(3) + .HasColumnType("decimal(3,1)"); + + b.Property("Bedrooms") + .HasMaxLength(3) + .HasColumnType("INTEGER"); + + b.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("IsAvailable") + .HasColumnType("INTEGER"); + + b.Property("IsDeleted") + .HasColumnType("INTEGER"); + + b.Property("LastModifiedBy") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LastModifiedOn") + .HasColumnType("TEXT"); + + b.Property("LastRoutineInspectionDate") + .HasColumnType("TEXT"); + + b.Property("MonthlyRent") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("NextRoutineInspectionDueDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("PropertyType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("RoutineInspectionIntervalMonths") + .HasColumnType("INTEGER"); + + b.Property("SquareFeet") + .HasMaxLength(7) + .HasColumnType("INTEGER"); + + b.Property("State") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("UnitNumber") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("ZipCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Address"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Properties"); + }); + + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.ProspectiveTenant", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("DateOfBirth") + .HasColumnType("TEXT"); + + b.Property("DesiredMoveInDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("FirstContactedOn") + .HasColumnType("TEXT"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("IdentificationNumber") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("IdentificationState") + .HasMaxLength(2) + .HasColumnType("TEXT"); + + b.Property("InterestedPropertyId") + .HasColumnType("TEXT"); + + b.Property("IsDeleted") + .HasColumnType("INTEGER"); + + b.Property("LastModifiedBy") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LastModifiedOn") + .HasColumnType("TEXT"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Phone") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Source") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Email"); + + b.HasIndex("InterestedPropertyId"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("Status"); + + b.ToTable("ProspectiveTenants"); + }); + + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.RentalApplication", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ApplicationFee") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("ApplicationFeePaid") + .HasColumnType("INTEGER"); + + b.Property("ApplicationFeePaidOn") + .HasColumnType("TEXT"); + + b.Property("ApplicationFeePaymentMethod") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("AppliedOn") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("CurrentAddress") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("CurrentCity") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CurrentRent") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("CurrentState") + .IsRequired() + .HasMaxLength(2) + .HasColumnType("TEXT"); + + b.Property("CurrentZipCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("DecidedOn") + .HasColumnType("TEXT"); + + b.Property("DecisionBy") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("DenialReason") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("EmployerName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("EmploymentLengthMonths") + .HasColumnType("INTEGER"); + + b.Property("ExpiresOn") + .HasColumnType("TEXT"); + + b.Property("IsDeleted") + .HasColumnType("INTEGER"); + + b.Property("JobTitle") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LandlordName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("LandlordPhone") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("LastModifiedBy") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LastModifiedOn") + .HasColumnType("TEXT"); + + b.Property("MonthlyIncome") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("OrganizationId") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("PropertyId") + .HasColumnType("TEXT"); + + b.Property("ProspectiveTenantId") + .HasColumnType("TEXT"); + + b.Property("Reference1Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Reference1Phone") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Reference1Relationship") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Reference2Name") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Reference2Phone") + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Reference2Relationship") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppliedOn"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("PropertyId"); + + b.HasIndex("ProspectiveTenantId"); + + b.HasIndex("Status"); + + b.ToTable("RentalApplications"); + }); + + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.SchemaVersion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AppliedOn") + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("SchemaVersions"); + }); + + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.SecurityDeposit", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("DateReceived") + .HasColumnType("TEXT"); + + b.Property("DeductionsAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("DeductionsReason") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("InInvestmentPool") + .HasColumnType("INTEGER"); + + b.Property("IsDeleted") + .HasColumnType("INTEGER"); + + b.Property("LastModifiedBy") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LastModifiedOn") + .HasColumnType("TEXT"); + + b.Property("LeaseId") + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("PaymentMethod") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PoolEntryDate") + .HasColumnType("TEXT"); + + b.Property("PoolExitDate") + .HasColumnType("TEXT"); + + b.Property("RefundAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("RefundMethod") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("RefundProcessedDate") + .HasColumnType("TEXT"); + + b.Property("RefundReference") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("TransactionReference") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("InInvestmentPool"); + + b.HasIndex("LeaseId") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("TenantId"); + + b.ToTable("SecurityDeposits"); + }); + + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.SecurityDepositDividend", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("BaseDividendAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("ChoiceMadeOn") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("DividendAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("InvestmentPoolId") + .HasColumnType("TEXT"); + + b.Property("IsDeleted") + .HasColumnType("INTEGER"); + + b.Property("LastModifiedBy") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LastModifiedOn") + .HasColumnType("TEXT"); + + b.Property("LeaseId") + .HasColumnType("TEXT"); + + b.Property("MailingAddress") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("MonthsInPool") + .HasColumnType("INTEGER"); + + b.Property("Notes") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("PaymentMethod") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PaymentProcessedOn") + .HasColumnType("TEXT"); + + b.Property("PaymentReference") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ProrationFactor") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.Property("SecurityDepositId") + .HasColumnType("TEXT"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.Property("Year") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("InvestmentPoolId"); + + b.HasIndex("LeaseId"); + + b.HasIndex("SecurityDepositId"); + + b.HasIndex("Status"); + + b.HasIndex("TenantId"); + + b.HasIndex("Year"); + + b.ToTable("SecurityDepositDividends"); + }); + + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.SecurityDepositInvestmentPool", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ActiveLeaseCount") + .HasColumnType("INTEGER"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("DividendPerLease") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("DividendsCalculatedOn") + .HasColumnType("TEXT"); + + b.Property("DividendsDistributedOn") + .HasColumnType("TEXT"); + + b.Property("EndingBalance") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("IsDeleted") + .HasColumnType("INTEGER"); + + b.Property("LastModifiedBy") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LastModifiedOn") + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("OrganizationShare") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("OrganizationSharePercentage") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.Property("ReturnRate") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.Property("StartingBalance") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("TenantShareTotal") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("TotalEarnings") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("Year") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("Status"); + + b.HasIndex("Year") + .IsUnique(); + + b.ToTable("SecurityDepositInvestmentPools"); + }); + + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.Tenant", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("DateOfBirth") + .HasColumnType("TEXT"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("EmergencyContactName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("EmergencyContactPhone") + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("IdentificationNumber") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("IsDeleted") + .HasColumnType("INTEGER"); + + b.Property("LastModifiedBy") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LastModifiedOn") + .HasColumnType("TEXT"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Notes") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("ProspectiveTenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique(); + + b.HasIndex("IdentificationNumber") + .IsUnique(); + + b.HasIndex("OrganizationId"); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.Tour", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CalendarEventId") + .HasColumnType("TEXT"); + + b.Property("ChecklistId") + .HasColumnType("TEXT"); + + b.Property("ConductedBy") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("DurationMinutes") + .HasColumnType("INTEGER"); + + b.Property("Feedback") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("InterestLevel") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("IsDeleted") + .HasColumnType("INTEGER"); + + b.Property("LastModifiedBy") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LastModifiedOn") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PropertyId") + .HasColumnType("TEXT"); + + b.Property("ProspectiveTenantId") + .HasColumnType("TEXT"); + + b.Property("ScheduledOn") + .HasColumnType("TEXT"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ChecklistId"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("PropertyId"); + + b.HasIndex("ProspectiveTenantId"); + + b.HasIndex("ScheduledOn"); + + b.HasIndex("Status"); + + b.ToTable("Tours"); + }); + + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.UserOrganization", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("GrantedBy") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("GrantedOn") + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("IsDeleted") + .HasColumnType("INTEGER"); + + b.Property("LastModifiedBy") + .HasColumnType("TEXT"); + + b.Property("LastModifiedOn") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevokedOn") + .HasColumnType("TEXT"); + + b.Property("Role") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("GrantedBy"); + + b.HasIndex("IsActive"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("Role"); + + b.HasIndex("UserId", "OrganizationId") + .IsUnique(); + + b.ToTable("UserOrganizations"); + }); + + modelBuilder.Entity("Aquiis.SimpleStart.Shared.Components.Account.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ActiveOrganizationId") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("LastLoginDate") + .HasColumnType("TEXT"); + + b.Property("LastLoginIP") + .HasColumnType("TEXT"); + + b.Property("LastName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("LoginCount") + .HasColumnType("INTEGER"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("PreviousLoginDate") + .HasColumnType("TEXT"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("Notification", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Category") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("EmailError") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("EmailSent") + .HasColumnType("INTEGER"); + + b.Property("EmailSentOn") + .HasColumnType("TEXT"); + + b.Property("IsDeleted") + .HasColumnType("INTEGER"); + + b.Property("IsRead") + .HasColumnType("INTEGER"); + + b.Property("LastModifiedBy") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LastModifiedOn") + .HasColumnType("TEXT"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("ReadOn") + .HasColumnType("TEXT"); + + b.Property("RecipientUserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("RelatedEntityId") + .HasColumnType("TEXT"); + + b.Property("RelatedEntityType") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("SMSError") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("SMSSent") + .HasColumnType("INTEGER"); + + b.Property("SMSSentOn") + .HasColumnType("TEXT"); + + b.Property("SendEmail") + .HasColumnType("INTEGER"); + + b.Property("SendInApp") + .HasColumnType("INTEGER"); + + b.Property("SendSMS") + .HasColumnType("INTEGER"); + + b.Property("SentOn") + .HasColumnType("TEXT"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Category"); + + b.HasIndex("IsRead"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("RecipientUserId"); + + b.HasIndex("SentOn"); + + b.ToTable("Notifications"); + }); + + modelBuilder.Entity("NotificationPreferences", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("DailyDigestTime") + .HasColumnType("TEXT"); + + b.Property("EmailAddress") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("EmailApplicationStatusChange") + .HasColumnType("INTEGER"); + + b.Property("EmailInspectionScheduled") + .HasColumnType("INTEGER"); + + b.Property("EmailLeaseExpiring") + .HasColumnType("INTEGER"); + + b.Property("EmailMaintenanceUpdate") + .HasColumnType("INTEGER"); + + b.Property("EmailPaymentDue") + .HasColumnType("INTEGER"); + + b.Property("EmailPaymentReceived") + .HasColumnType("INTEGER"); + + b.Property("EnableDailyDigest") + .HasColumnType("INTEGER"); + + b.Property("EnableEmailNotifications") + .HasColumnType("INTEGER"); + + b.Property("EnableInAppNotifications") + .HasColumnType("INTEGER"); + + b.Property("EnableSMSNotifications") + .HasColumnType("INTEGER"); + + b.Property("EnableWeeklyDigest") + .HasColumnType("INTEGER"); + + b.Property("IsDeleted") + .HasColumnType("INTEGER"); + + b.Property("LastModifiedBy") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LastModifiedOn") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("SMSLeaseExpiringUrgent") + .HasColumnType("INTEGER"); + + b.Property("SMSMaintenanceEmergency") + .HasColumnType("INTEGER"); + + b.Property("SMSPaymentDue") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("WeeklyDigestDay") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.HasIndex("UserId", "OrganizationId") + .IsUnique(); + + b.ToTable("NotificationPreferences"); + }); + + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.ApplicationScreening", b => + { + b.HasOne("Aquiis.SimpleStart.Core.Entities.RentalApplication", "RentalApplication") + .WithOne("Screening") + .HasForeignKey("Aquiis.SimpleStart.Core.Entities.ApplicationScreening", "RentalApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RentalApplication"); + }); + + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.CalendarEvent", b => + { + b.HasOne("Aquiis.SimpleStart.Core.Entities.Property", "Property") + .WithMany() + .HasForeignKey("PropertyId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Property"); + }); + + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.Checklist", b => + { + b.HasOne("Aquiis.SimpleStart.Core.Entities.ChecklistTemplate", "ChecklistTemplate") + .WithMany("Checklists") + .HasForeignKey("ChecklistTemplateId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Aquiis.SimpleStart.Core.Entities.Document", "Document") + .WithMany() + .HasForeignKey("DocumentId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Aquiis.SimpleStart.Core.Entities.Lease", "Lease") + .WithMany() + .HasForeignKey("LeaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Aquiis.SimpleStart.Core.Entities.Property", "Property") + .WithMany() + .HasForeignKey("PropertyId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("ChecklistTemplate"); + + b.Navigation("Document"); + + b.Navigation("Lease"); + + b.Navigation("Property"); + }); + + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.ChecklistItem", b => + { + b.HasOne("Aquiis.SimpleStart.Core.Entities.Checklist", "Checklist") + .WithMany("Items") + .HasForeignKey("ChecklistId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Checklist"); + }); + + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.ChecklistTemplateItem", b => + { + b.HasOne("Aquiis.SimpleStart.Core.Entities.ChecklistTemplate", "ChecklistTemplate") + .WithMany("Items") + .HasForeignKey("ChecklistTemplateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ChecklistTemplate"); + }); + + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.Document", b => + { + b.HasOne("Aquiis.SimpleStart.Core.Entities.Invoice", "Invoice") + .WithMany() + .HasForeignKey("InvoiceId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Aquiis.SimpleStart.Core.Entities.Lease", "Lease") + .WithMany("Documents") + .HasForeignKey("LeaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Aquiis.SimpleStart.Core.Entities.Organization", null) + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Aquiis.SimpleStart.Core.Entities.Payment", "Payment") + .WithMany() + .HasForeignKey("PaymentId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Aquiis.SimpleStart.Core.Entities.Property", "Property") + .WithMany("Documents") + .HasForeignKey("PropertyId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Aquiis.SimpleStart.Core.Entities.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Invoice"); + + b.Navigation("Lease"); + + b.Navigation("Payment"); + + b.Navigation("Property"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.Inspection", b => + { + b.HasOne("Aquiis.SimpleStart.Core.Entities.Document", "Document") + .WithMany() + .HasForeignKey("DocumentId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Aquiis.SimpleStart.Core.Entities.Lease", "Lease") + .WithMany() + .HasForeignKey("LeaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Aquiis.SimpleStart.Core.Entities.Property", "Property") + .WithMany() + .HasForeignKey("PropertyId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Document"); + + b.Navigation("Lease"); + + b.Navigation("Property"); + }); + + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.Invoice", b => + { + b.HasOne("Aquiis.SimpleStart.Core.Entities.Document", "Document") + .WithMany() + .HasForeignKey("DocumentId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Aquiis.SimpleStart.Core.Entities.Lease", "Lease") + .WithMany("Invoices") + .HasForeignKey("LeaseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Aquiis.SimpleStart.Core.Entities.Organization", null) + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Document"); + + b.Navigation("Lease"); + }); + + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.Lease", b => + { + b.HasOne("Aquiis.SimpleStart.Core.Entities.Document", "Document") + .WithMany() + .HasForeignKey("DocumentId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Aquiis.SimpleStart.Core.Entities.Organization", null) + .WithMany("Leases") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Aquiis.SimpleStart.Core.Entities.Property", "Property") + .WithMany("Leases") + .HasForeignKey("PropertyId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Aquiis.SimpleStart.Core.Entities.Tenant", "Tenant") + .WithMany("Leases") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Document"); + + b.Navigation("Property"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.LeaseOffer", b => + { + b.HasOne("Aquiis.SimpleStart.Core.Entities.Property", "Property") + .WithMany() + .HasForeignKey("PropertyId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Aquiis.SimpleStart.Core.Entities.ProspectiveTenant", "ProspectiveTenant") + .WithMany() + .HasForeignKey("ProspectiveTenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Aquiis.SimpleStart.Core.Entities.RentalApplication", "RentalApplication") + .WithMany() + .HasForeignKey("RentalApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Property"); + + b.Navigation("ProspectiveTenant"); + + b.Navigation("RentalApplication"); + }); + + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.MaintenanceRequest", b => + { + b.HasOne("Aquiis.SimpleStart.Core.Entities.Lease", "Lease") + .WithMany() + .HasForeignKey("LeaseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Aquiis.SimpleStart.Core.Entities.Property", "Property") + .WithMany() + .HasForeignKey("PropertyId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Lease"); + + b.Navigation("Property"); + }); + + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.Note", b => + { + b.HasOne("Aquiis.SimpleStart.Shared.Components.Account.ApplicationUser", "User") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.Organization", b => + { + b.HasOne("Aquiis.SimpleStart.Shared.Components.Account.ApplicationUser", null) + .WithMany() + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.Payment", b => + { + b.HasOne("Aquiis.SimpleStart.Core.Entities.Document", "Document") + .WithMany() + .HasForeignKey("DocumentId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Aquiis.SimpleStart.Core.Entities.Invoice", "Invoice") + .WithMany("Payments") + .HasForeignKey("InvoiceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Aquiis.SimpleStart.Core.Entities.Organization", null) + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Document"); + + b.Navigation("Invoice"); + }); + + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.Property", b => + { + b.HasOne("Aquiis.SimpleStart.Core.Entities.Organization", null) + .WithMany("Properties") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + }); + + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.ProspectiveTenant", b => + { + b.HasOne("Aquiis.SimpleStart.Core.Entities.Property", "InterestedProperty") + .WithMany() + .HasForeignKey("InterestedPropertyId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("InterestedProperty"); + }); + + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.RentalApplication", b => + { + b.HasOne("Aquiis.SimpleStart.Core.Entities.Property", "Property") + .WithMany() + .HasForeignKey("PropertyId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Aquiis.SimpleStart.Core.Entities.ProspectiveTenant", "ProspectiveTenant") + .WithMany("Applications") + .HasForeignKey("ProspectiveTenantId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Property"); + + b.Navigation("ProspectiveTenant"); + }); + + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.SecurityDeposit", b => + { + b.HasOne("Aquiis.SimpleStart.Core.Entities.Lease", "Lease") + .WithMany() + .HasForeignKey("LeaseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Aquiis.SimpleStart.Core.Entities.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Lease"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.SecurityDepositDividend", b => + { + b.HasOne("Aquiis.SimpleStart.Core.Entities.SecurityDepositInvestmentPool", "InvestmentPool") + .WithMany("Dividends") + .HasForeignKey("InvestmentPoolId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Aquiis.SimpleStart.Core.Entities.Lease", "Lease") + .WithMany() + .HasForeignKey("LeaseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Aquiis.SimpleStart.Core.Entities.SecurityDeposit", "SecurityDeposit") + .WithMany("Dividends") + .HasForeignKey("SecurityDepositId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Aquiis.SimpleStart.Core.Entities.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("InvestmentPool"); + + b.Navigation("Lease"); + + b.Navigation("SecurityDeposit"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.Tenant", b => + { + b.HasOne("Aquiis.SimpleStart.Core.Entities.Organization", null) + .WithMany("Tenants") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + }); + + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.Tour", b => + { + b.HasOne("Aquiis.SimpleStart.Core.Entities.Checklist", "Checklist") + .WithMany() + .HasForeignKey("ChecklistId"); + + b.HasOne("Aquiis.SimpleStart.Core.Entities.Property", "Property") + .WithMany() + .HasForeignKey("PropertyId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Aquiis.SimpleStart.Core.Entities.ProspectiveTenant", "ProspectiveTenant") + .WithMany("Tours") + .HasForeignKey("ProspectiveTenantId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Checklist"); + + b.Navigation("Property"); + + b.Navigation("ProspectiveTenant"); + }); + + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.UserOrganization", b => + { + b.HasOne("Aquiis.SimpleStart.Shared.Components.Account.ApplicationUser", null) + .WithMany() + .HasForeignKey("GrantedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Aquiis.SimpleStart.Core.Entities.Organization", "Organization") + .WithMany("UserOrganizations") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Aquiis.SimpleStart.Shared.Components.Account.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Aquiis.SimpleStart.Shared.Components.Account.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Aquiis.SimpleStart.Shared.Components.Account.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Aquiis.SimpleStart.Shared.Components.Account.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Aquiis.SimpleStart.Shared.Components.Account.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Notification", b => + { + b.HasOne("Aquiis.SimpleStart.Core.Entities.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Aquiis.SimpleStart.Shared.Components.Account.ApplicationUser", null) + .WithMany() + .HasForeignKey("RecipientUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("NotificationPreferences", b => + { + b.HasOne("Aquiis.SimpleStart.Core.Entities.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Aquiis.SimpleStart.Shared.Components.Account.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.Checklist", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.ChecklistTemplate", b => + { + b.Navigation("Checklists"); + + b.Navigation("Items"); + }); + + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.Invoice", b => + { + b.Navigation("Payments"); + }); + + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.Lease", b => + { + b.Navigation("Documents"); + + b.Navigation("Invoices"); + }); + + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.Organization", b => + { + b.Navigation("Leases"); + + b.Navigation("Properties"); + + b.Navigation("Tenants"); + + b.Navigation("UserOrganizations"); + }); + + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.Property", b => + { + b.Navigation("Documents"); + + b.Navigation("Leases"); + }); + + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.ProspectiveTenant", b => + { + b.Navigation("Applications"); + + b.Navigation("Tours"); + }); + + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.RentalApplication", b => + { + b.Navigation("Screening"); + }); + + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.SecurityDeposit", b => + { + b.Navigation("Dividends"); + }); + + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.SecurityDepositInvestmentPool", b => + { + b.Navigation("Dividends"); + }); + + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.Tenant", b => + { + b.Navigation("Leases"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Aquiis.SimpleStart/Infrastructure/Data/Migrations/20251229235707_AddNotificationInfrastructure.cs b/Aquiis.SimpleStart/Infrastructure/Data/Migrations/20251229235707_AddNotificationInfrastructure.cs new file mode 100644 index 0000000..5a84a1e --- /dev/null +++ b/Aquiis.SimpleStart/Infrastructure/Data/Migrations/20251229235707_AddNotificationInfrastructure.cs @@ -0,0 +1,666 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Aquiis.SimpleStart.Migrations +{ + /// + public partial class AddNotificationInfrastructure : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "NotificationPreferences", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + OrganizationId = table.Column(type: "TEXT", nullable: false), + UserId = table.Column(type: "TEXT", nullable: false), + EnableInAppNotifications = table.Column(type: "INTEGER", nullable: false), + EnableEmailNotifications = table.Column(type: "INTEGER", nullable: false), + EmailAddress = table.Column(type: "TEXT", maxLength: 200, nullable: true), + EmailLeaseExpiring = table.Column(type: "INTEGER", nullable: false), + EmailPaymentDue = table.Column(type: "INTEGER", nullable: false), + EmailPaymentReceived = table.Column(type: "INTEGER", nullable: false), + EmailApplicationStatusChange = table.Column(type: "INTEGER", nullable: false), + EmailMaintenanceUpdate = table.Column(type: "INTEGER", nullable: false), + EmailInspectionScheduled = table.Column(type: "INTEGER", nullable: false), + EnableSMSNotifications = table.Column(type: "INTEGER", nullable: false), + PhoneNumber = table.Column(type: "TEXT", maxLength: 20, nullable: true), + SMSPaymentDue = table.Column(type: "INTEGER", nullable: false), + SMSMaintenanceEmergency = table.Column(type: "INTEGER", nullable: false), + SMSLeaseExpiringUrgent = table.Column(type: "INTEGER", nullable: false), + EnableDailyDigest = table.Column(type: "INTEGER", nullable: false), + DailyDigestTime = table.Column(type: "TEXT", nullable: false), + EnableWeeklyDigest = table.Column(type: "INTEGER", nullable: false), + WeeklyDigestDay = table.Column(type: "INTEGER", nullable: false), + CreatedOn = table.Column(type: "TEXT", nullable: false), + CreatedBy = table.Column(type: "TEXT", maxLength: 100, nullable: false), + LastModifiedOn = table.Column(type: "TEXT", nullable: true), + LastModifiedBy = table.Column(type: "TEXT", maxLength: 100, nullable: true), + IsDeleted = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_NotificationPreferences", x => x.Id); + table.ForeignKey( + name: "FK_NotificationPreferences_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_NotificationPreferences_Organizations_OrganizationId", + column: x => x.OrganizationId, + principalTable: "Organizations", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Notifications", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + OrganizationId = table.Column(type: "TEXT", nullable: false), + Title = table.Column(type: "TEXT", maxLength: 200, nullable: false), + Message = table.Column(type: "TEXT", maxLength: 2000, nullable: false), + Type = table.Column(type: "TEXT", maxLength: 50, nullable: false), + Category = table.Column(type: "TEXT", maxLength: 50, nullable: false), + RecipientUserId = table.Column(type: "TEXT", nullable: false), + SentOn = table.Column(type: "TEXT", nullable: false), + ReadOn = table.Column(type: "TEXT", nullable: true), + IsRead = table.Column(type: "INTEGER", nullable: false), + RelatedEntityId = table.Column(type: "TEXT", nullable: true), + RelatedEntityType = table.Column(type: "TEXT", maxLength: 50, nullable: true), + SendInApp = table.Column(type: "INTEGER", nullable: false), + SendEmail = table.Column(type: "INTEGER", nullable: false), + SendSMS = table.Column(type: "INTEGER", nullable: false), + EmailSent = table.Column(type: "INTEGER", nullable: false), + EmailSentOn = table.Column(type: "TEXT", nullable: true), + SMSSent = table.Column(type: "INTEGER", nullable: false), + SMSSentOn = table.Column(type: "TEXT", nullable: true), + EmailError = table.Column(type: "TEXT", maxLength: 500, nullable: true), + SMSError = table.Column(type: "TEXT", maxLength: 500, nullable: true), + CreatedOn = table.Column(type: "TEXT", nullable: false), + CreatedBy = table.Column(type: "TEXT", maxLength: 100, nullable: false), + LastModifiedOn = table.Column(type: "TEXT", nullable: true), + LastModifiedBy = table.Column(type: "TEXT", maxLength: 100, nullable: true), + IsDeleted = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Notifications", x => x.Id); + table.ForeignKey( + name: "FK_Notifications_AspNetUsers_RecipientUserId", + column: x => x.RecipientUserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Notifications_Organizations_OrganizationId", + column: x => x.OrganizationId, + principalTable: "Organizations", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000001"), + column: "LastModifiedBy", + value: null); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000002"), + column: "LastModifiedBy", + value: null); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000003"), + column: "LastModifiedBy", + value: null); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000004"), + column: "LastModifiedBy", + value: null); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000005"), + column: "LastModifiedBy", + value: null); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000006"), + column: "LastModifiedBy", + value: null); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000007"), + column: "LastModifiedBy", + value: null); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000008"), + column: "LastModifiedBy", + value: null); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000009"), + column: "LastModifiedBy", + value: null); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000010"), + column: "LastModifiedBy", + value: null); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000011"), + column: "LastModifiedBy", + value: null); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000012"), + column: "LastModifiedBy", + value: null); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000013"), + column: "LastModifiedBy", + value: null); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000014"), + column: "LastModifiedBy", + value: null); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000015"), + column: "LastModifiedBy", + value: null); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000016"), + column: "LastModifiedBy", + value: null); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000017"), + column: "LastModifiedBy", + value: null); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000018"), + column: "LastModifiedBy", + value: null); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000019"), + column: "LastModifiedBy", + value: null); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000020"), + column: "LastModifiedBy", + value: null); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000021"), + column: "LastModifiedBy", + value: null); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000022"), + column: "LastModifiedBy", + value: null); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000023"), + column: "LastModifiedBy", + value: null); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000024"), + column: "LastModifiedBy", + value: null); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000025"), + column: "LastModifiedBy", + value: null); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000026"), + column: "LastModifiedBy", + value: null); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000027"), + column: "LastModifiedBy", + value: null); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000028"), + column: "LastModifiedBy", + value: null); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000029"), + column: "LastModifiedBy", + value: null); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000030"), + column: "LastModifiedBy", + value: null); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000031"), + column: "LastModifiedBy", + value: null); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000032"), + column: "LastModifiedBy", + value: null); + + migrationBuilder.UpdateData( + table: "ChecklistTemplates", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0001-000000000001"), + column: "LastModifiedBy", + value: null); + + migrationBuilder.UpdateData( + table: "ChecklistTemplates", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0001-000000000002"), + column: "LastModifiedBy", + value: null); + + migrationBuilder.UpdateData( + table: "ChecklistTemplates", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0001-000000000003"), + column: "LastModifiedBy", + value: null); + + migrationBuilder.UpdateData( + table: "ChecklistTemplates", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0001-000000000004"), + column: "LastModifiedBy", + value: null); + + migrationBuilder.CreateIndex( + name: "IX_NotificationPreferences_OrganizationId", + table: "NotificationPreferences", + column: "OrganizationId"); + + migrationBuilder.CreateIndex( + name: "IX_NotificationPreferences_UserId", + table: "NotificationPreferences", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_NotificationPreferences_UserId_OrganizationId", + table: "NotificationPreferences", + columns: new[] { "UserId", "OrganizationId" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Notifications_Category", + table: "Notifications", + column: "Category"); + + migrationBuilder.CreateIndex( + name: "IX_Notifications_IsRead", + table: "Notifications", + column: "IsRead"); + + migrationBuilder.CreateIndex( + name: "IX_Notifications_OrganizationId", + table: "Notifications", + column: "OrganizationId"); + + migrationBuilder.CreateIndex( + name: "IX_Notifications_RecipientUserId", + table: "Notifications", + column: "RecipientUserId"); + + migrationBuilder.CreateIndex( + name: "IX_Notifications_SentOn", + table: "Notifications", + column: "SentOn"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "NotificationPreferences"); + + migrationBuilder.DropTable( + name: "Notifications"); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000001"), + column: "LastModifiedBy", + value: ""); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000002"), + column: "LastModifiedBy", + value: ""); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000003"), + column: "LastModifiedBy", + value: ""); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000004"), + column: "LastModifiedBy", + value: ""); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000005"), + column: "LastModifiedBy", + value: ""); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000006"), + column: "LastModifiedBy", + value: ""); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000007"), + column: "LastModifiedBy", + value: ""); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000008"), + column: "LastModifiedBy", + value: ""); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000009"), + column: "LastModifiedBy", + value: ""); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000010"), + column: "LastModifiedBy", + value: ""); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000011"), + column: "LastModifiedBy", + value: ""); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000012"), + column: "LastModifiedBy", + value: ""); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000013"), + column: "LastModifiedBy", + value: ""); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000014"), + column: "LastModifiedBy", + value: ""); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000015"), + column: "LastModifiedBy", + value: ""); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000016"), + column: "LastModifiedBy", + value: ""); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000017"), + column: "LastModifiedBy", + value: ""); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000018"), + column: "LastModifiedBy", + value: ""); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000019"), + column: "LastModifiedBy", + value: ""); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000020"), + column: "LastModifiedBy", + value: ""); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000021"), + column: "LastModifiedBy", + value: ""); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000022"), + column: "LastModifiedBy", + value: ""); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000023"), + column: "LastModifiedBy", + value: ""); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000024"), + column: "LastModifiedBy", + value: ""); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000025"), + column: "LastModifiedBy", + value: ""); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000026"), + column: "LastModifiedBy", + value: ""); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000027"), + column: "LastModifiedBy", + value: ""); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000028"), + column: "LastModifiedBy", + value: ""); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000029"), + column: "LastModifiedBy", + value: ""); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000030"), + column: "LastModifiedBy", + value: ""); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000031"), + column: "LastModifiedBy", + value: ""); + + migrationBuilder.UpdateData( + table: "ChecklistTemplateItems", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0002-000000000032"), + column: "LastModifiedBy", + value: ""); + + migrationBuilder.UpdateData( + table: "ChecklistTemplates", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0001-000000000001"), + column: "LastModifiedBy", + value: ""); + + migrationBuilder.UpdateData( + table: "ChecklistTemplates", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0001-000000000002"), + column: "LastModifiedBy", + value: ""); + + migrationBuilder.UpdateData( + table: "ChecklistTemplates", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0001-000000000003"), + column: "LastModifiedBy", + value: ""); + + migrationBuilder.UpdateData( + table: "ChecklistTemplates", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0001-000000000004"), + column: "LastModifiedBy", + value: ""); + } + } +} diff --git a/Aquiis.SimpleStart/Infrastructure/Data/Migrations/ApplicationDbContextModelSnapshot.cs b/Aquiis.SimpleStart/Infrastructure/Data/Migrations/ApplicationDbContextModelSnapshot.cs index 2c59203..6394bdf 100644 --- a/Aquiis.SimpleStart/Infrastructure/Data/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/Aquiis.SimpleStart/Infrastructure/Data/Migrations/ApplicationDbContextModelSnapshot.cs @@ -541,7 +541,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) Description = "Standard property showing checklist", IsDeleted = false, IsSystemTemplate = true, - LastModifiedBy = "", Name = "Property Tour", OrganizationId = new Guid("00000000-0000-0000-0000-000000000000") }, @@ -554,7 +553,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) Description = "Move-in inspection checklist", IsDeleted = false, IsSystemTemplate = true, - LastModifiedBy = "", Name = "Move-In", OrganizationId = new Guid("00000000-0000-0000-0000-000000000000") }, @@ -567,7 +565,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) Description = "Move-out inspection checklist", IsDeleted = false, IsSystemTemplate = true, - LastModifiedBy = "", Name = "Move-Out", OrganizationId = new Guid("00000000-0000-0000-0000-000000000000") }, @@ -580,7 +577,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) Description = "Open house event checklist", IsDeleted = false, IsSystemTemplate = true, - LastModifiedBy = "", Name = "Open House", OrganizationId = new Guid("00000000-0000-0000-0000-000000000000") }); @@ -658,7 +654,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) IsRequired = true, ItemOrder = 1, ItemText = "Greeted prospect and verified appointment", - LastModifiedBy = "", OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), RequiresValue = false, SectionOrder = 1 @@ -675,7 +670,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) IsRequired = true, ItemOrder = 2, ItemText = "Reviewed property exterior and curb appeal", - LastModifiedBy = "", OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), RequiresValue = false, SectionOrder = 1 @@ -692,7 +686,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) IsRequired = true, ItemOrder = 3, ItemText = "Showed parking area/garage", - LastModifiedBy = "", OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), RequiresValue = false, SectionOrder = 1 @@ -709,7 +702,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) IsRequired = true, ItemOrder = 4, ItemText = "Toured living room/common areas", - LastModifiedBy = "", OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), RequiresValue = false, SectionOrder = 2 @@ -726,7 +718,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) IsRequired = true, ItemOrder = 5, ItemText = "Showed all bedrooms", - LastModifiedBy = "", OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), RequiresValue = false, SectionOrder = 2 @@ -743,7 +734,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) IsRequired = true, ItemOrder = 6, ItemText = "Showed all bathrooms", - LastModifiedBy = "", OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), RequiresValue = false, SectionOrder = 2 @@ -760,7 +750,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) IsRequired = true, ItemOrder = 7, ItemText = "Toured kitchen and demonstrated appliances", - LastModifiedBy = "", OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), RequiresValue = false, SectionOrder = 3 @@ -777,7 +766,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) IsRequired = true, ItemOrder = 8, ItemText = "Explained which appliances are included", - LastModifiedBy = "", OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), RequiresValue = false, SectionOrder = 3 @@ -794,7 +782,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) IsRequired = true, ItemOrder = 9, ItemText = "Explained HVAC system and thermostat controls", - LastModifiedBy = "", OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), RequiresValue = false, SectionOrder = 4 @@ -811,7 +798,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) IsRequired = true, ItemOrder = 10, ItemText = "Reviewed utility responsibilities (tenant vs landlord)", - LastModifiedBy = "", OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), RequiresValue = false, SectionOrder = 4 @@ -828,7 +814,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) IsRequired = true, ItemOrder = 11, ItemText = "Showed water heater location", - LastModifiedBy = "", OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), RequiresValue = false, SectionOrder = 4 @@ -845,7 +830,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) IsRequired = true, ItemOrder = 12, ItemText = "Showed storage areas (closets, attic, basement)", - LastModifiedBy = "", OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), RequiresValue = false, SectionOrder = 5 @@ -862,7 +846,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) IsRequired = true, ItemOrder = 13, ItemText = "Showed laundry facilities", - LastModifiedBy = "", OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), RequiresValue = false, SectionOrder = 5 @@ -879,7 +862,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) IsRequired = true, ItemOrder = 14, ItemText = "Showed outdoor space (yard, patio, balcony)", - LastModifiedBy = "", OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), RequiresValue = false, SectionOrder = 5 @@ -896,7 +878,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) IsRequired = true, ItemOrder = 15, ItemText = "Discussed monthly rent amount", - LastModifiedBy = "", OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), RequiresValue = false, SectionOrder = 6 @@ -913,7 +894,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) IsRequired = true, ItemOrder = 16, ItemText = "Explained security deposit and move-in costs", - LastModifiedBy = "", OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), RequiresValue = false, SectionOrder = 6 @@ -930,7 +910,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) IsRequired = true, ItemOrder = 17, ItemText = "Reviewed lease term length and start date", - LastModifiedBy = "", OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), RequiresValue = false, SectionOrder = 6 @@ -947,7 +926,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) IsRequired = true, ItemOrder = 18, ItemText = "Explained pet policy", - LastModifiedBy = "", OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), RequiresValue = false, SectionOrder = 6 @@ -964,7 +942,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) IsRequired = true, ItemOrder = 19, ItemText = "Explained application process and requirements", - LastModifiedBy = "", OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), RequiresValue = false, SectionOrder = 7 @@ -981,7 +958,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) IsRequired = true, ItemOrder = 20, ItemText = "Reviewed screening process (background, credit check)", - LastModifiedBy = "", OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), RequiresValue = false, SectionOrder = 7 @@ -998,7 +974,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) IsRequired = true, ItemOrder = 21, ItemText = "Answered all prospect questions", - LastModifiedBy = "", OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), RequiresValue = false, SectionOrder = 7 @@ -1015,7 +990,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) IsRequired = true, ItemOrder = 22, ItemText = "Prospect Interest Level", - LastModifiedBy = "", OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), RequiresValue = true, SectionOrder = 8 @@ -1032,7 +1006,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) IsRequired = true, ItemOrder = 23, ItemText = "Overall showing feedback and notes", - LastModifiedBy = "", OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), RequiresValue = false, SectionOrder = 8 @@ -1049,7 +1022,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) IsRequired = true, ItemOrder = 1, ItemText = "Document property condition", - LastModifiedBy = "", OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), RequiresValue = false, SectionOrder = 1 @@ -1066,7 +1038,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) IsRequired = true, ItemOrder = 2, ItemText = "Collect keys and access codes", - LastModifiedBy = "", OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), RequiresValue = false, SectionOrder = 1 @@ -1083,7 +1054,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) IsRequired = true, ItemOrder = 3, ItemText = "Review lease terms with tenant", - LastModifiedBy = "", OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), RequiresValue = false, SectionOrder = 1 @@ -1100,7 +1070,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) IsRequired = true, ItemOrder = 1, ItemText = "Inspect property condition", - LastModifiedBy = "", OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), RequiresValue = false, SectionOrder = 1 @@ -1117,7 +1086,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) IsRequired = true, ItemOrder = 2, ItemText = "Collect all keys and access devices", - LastModifiedBy = "", OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), RequiresValue = false, SectionOrder = 1 @@ -1134,7 +1102,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) IsRequired = true, ItemOrder = 3, ItemText = "Document damages and needed repairs", - LastModifiedBy = "", OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), RequiresValue = false, SectionOrder = 1 @@ -1151,7 +1118,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) IsRequired = true, ItemOrder = 1, ItemText = "Set up signage and directional markers", - LastModifiedBy = "", OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), RequiresValue = false, SectionOrder = 1 @@ -1168,7 +1134,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) IsRequired = true, ItemOrder = 2, ItemText = "Prepare information packets", - LastModifiedBy = "", OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), RequiresValue = false, SectionOrder = 1 @@ -1185,7 +1150,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) IsRequired = true, ItemOrder = 3, ItemText = "Set up visitor sign-in sheet", - LastModifiedBy = "", OrganizationId = new Guid("00000000-0000-0000-0000-000000000000"), RequiresValue = false, SectionOrder = 1 @@ -3351,6 +3315,214 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("AspNetUserTokens", (string)null); }); + modelBuilder.Entity("Notification", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Category") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("EmailError") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("EmailSent") + .HasColumnType("INTEGER"); + + b.Property("EmailSentOn") + .HasColumnType("TEXT"); + + b.Property("IsDeleted") + .HasColumnType("INTEGER"); + + b.Property("IsRead") + .HasColumnType("INTEGER"); + + b.Property("LastModifiedBy") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LastModifiedOn") + .HasColumnType("TEXT"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("ReadOn") + .HasColumnType("TEXT"); + + b.Property("RecipientUserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("RelatedEntityId") + .HasColumnType("TEXT"); + + b.Property("RelatedEntityType") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("SMSError") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("SMSSent") + .HasColumnType("INTEGER"); + + b.Property("SMSSentOn") + .HasColumnType("TEXT"); + + b.Property("SendEmail") + .HasColumnType("INTEGER"); + + b.Property("SendInApp") + .HasColumnType("INTEGER"); + + b.Property("SendSMS") + .HasColumnType("INTEGER"); + + b.Property("SentOn") + .HasColumnType("TEXT"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Category"); + + b.HasIndex("IsRead"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("RecipientUserId"); + + b.HasIndex("SentOn"); + + b.ToTable("Notifications"); + }); + + modelBuilder.Entity("NotificationPreferences", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("DailyDigestTime") + .HasColumnType("TEXT"); + + b.Property("EmailAddress") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("EmailApplicationStatusChange") + .HasColumnType("INTEGER"); + + b.Property("EmailInspectionScheduled") + .HasColumnType("INTEGER"); + + b.Property("EmailLeaseExpiring") + .HasColumnType("INTEGER"); + + b.Property("EmailMaintenanceUpdate") + .HasColumnType("INTEGER"); + + b.Property("EmailPaymentDue") + .HasColumnType("INTEGER"); + + b.Property("EmailPaymentReceived") + .HasColumnType("INTEGER"); + + b.Property("EnableDailyDigest") + .HasColumnType("INTEGER"); + + b.Property("EnableEmailNotifications") + .HasColumnType("INTEGER"); + + b.Property("EnableInAppNotifications") + .HasColumnType("INTEGER"); + + b.Property("EnableSMSNotifications") + .HasColumnType("INTEGER"); + + b.Property("EnableWeeklyDigest") + .HasColumnType("INTEGER"); + + b.Property("IsDeleted") + .HasColumnType("INTEGER"); + + b.Property("LastModifiedBy") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LastModifiedOn") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("SMSLeaseExpiringUrgent") + .HasColumnType("INTEGER"); + + b.Property("SMSMaintenanceEmergency") + .HasColumnType("INTEGER"); + + b.Property("SMSPaymentDue") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("WeeklyDigestDay") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.HasIndex("UserId", "OrganizationId") + .IsUnique(); + + b.ToTable("NotificationPreferences"); + }); + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.ApplicationScreening", b => { b.HasOne("Aquiis.SimpleStart.Core.Entities.RentalApplication", "RentalApplication") @@ -3840,6 +4012,40 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsRequired(); }); + modelBuilder.Entity("Notification", b => + { + b.HasOne("Aquiis.SimpleStart.Core.Entities.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Aquiis.SimpleStart.Shared.Components.Account.ApplicationUser", null) + .WithMany() + .HasForeignKey("RecipientUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("NotificationPreferences", b => + { + b.HasOne("Aquiis.SimpleStart.Core.Entities.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Aquiis.SimpleStart.Shared.Components.Account.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + modelBuilder.Entity("Aquiis.SimpleStart.Core.Entities.Checklist", b => { b.Navigation("Items"); diff --git a/Aquiis.SimpleStart/Infrastructure/Services/EmailService.cs b/Aquiis.SimpleStart/Infrastructure/Services/EmailService.cs new file mode 100644 index 0000000..b340828 --- /dev/null +++ b/Aquiis.SimpleStart/Infrastructure/Services/EmailService.cs @@ -0,0 +1,41 @@ + +using Aquiis.SimpleStart.Core.Interfaces.Services; + +namespace Aquiis.SimpleStart.Infrastructure.Services; + +public class EmailService : IEmailService +{ + private readonly ILogger _logger; + private readonly IConfiguration _configuration; + + public EmailService(ILogger logger, IConfiguration configuration) + { + _logger = logger; + _configuration = configuration; + } + + public async Task SendEmailAsync(string to, string subject, string body) + { + // TODO: Implement with SendGrid/Mailgun in Task 2.5 + _logger.LogInformation($"[EMAIL] To: {to}, Subject: {subject}, Body: {body}"); + await Task.CompletedTask; + } + + public async Task SendEmailAsync(string to, string subject, string body, string? fromName = null) + { + _logger.LogInformation($"[EMAIL] From: {fromName}, To: {to}, Subject: {subject}"); + await Task.CompletedTask; + } + + public async Task SendTemplateEmailAsync(string to, string templateId, Dictionary templateData) + { + _logger.LogInformation($"[EMAIL TEMPLATE] To: {to}, Template: {templateId}"); + await Task.CompletedTask; + } + + public async Task ValidateEmailAddressAsync(string email) + { + // Basic validation + return await Task.FromResult(!string.IsNullOrWhiteSpace(email) && email.Contains("@")); + } +} \ No newline at end of file diff --git a/Aquiis.SimpleStart/Infrastructure/Services/SMSService.cs b/Aquiis.SimpleStart/Infrastructure/Services/SMSService.cs new file mode 100644 index 0000000..a248656 --- /dev/null +++ b/Aquiis.SimpleStart/Infrastructure/Services/SMSService.cs @@ -0,0 +1,28 @@ + +using Aquiis.SimpleStart.Core.Interfaces.Services; + +namespace Aquiis.SimpleStart.Infrastructure.Services; + +public class SMSService : ISMSService +{ + private readonly ILogger _logger; + + public SMSService(ILogger logger) + { + _logger = logger; + } + + public async Task SendSMSAsync(string phoneNumber, string message) + { + // TODO: Implement with Twilio in Task 2.5 + _logger.LogInformation($"[SMS] To: {phoneNumber}, Message: {message}"); + await Task.CompletedTask; + } + + public async Task ValidatePhoneNumberAsync(string phoneNumber) + { + // Basic validation + var digits = new string(phoneNumber.Where(char.IsDigit).ToArray()); + return await Task.FromResult(digits.Length >= 10); + } +} \ No newline at end of file diff --git a/Aquiis.SimpleStart/Program.cs b/Aquiis.SimpleStart/Program.cs index 0f7ec17..7858b24 100644 --- a/Aquiis.SimpleStart/Program.cs +++ b/Aquiis.SimpleStart/Program.cs @@ -14,6 +14,8 @@ using ElectronNET.API; using Microsoft.Extensions.Options; using Aquiis.SimpleStart.Application.Services.Workflows; +using Aquiis.SimpleStart.Core.Interfaces.Services; +using Aquiis.SimpleStart.Infrastructure.Services; var builder = WebApplication.CreateBuilder(args); @@ -163,6 +165,12 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +// Add to service registration section +builder.Services.AddScoped(); + +// Phase 2.4: Notification Infrastructure +builder.Services.AddScoped(); +builder.Services.AddScoped(); // Workflow services builder.Services.AddScoped(); From a9ddeb63c30687a9540c804697ed679a0c91ca12 Mon Sep 17 00:00:00 2001 From: CIS Guru Date: Mon, 29 Dec 2025 18:13:16 -0600 Subject: [PATCH 2/2] Phase 2.4 notification infrastructure complete --- ...pplicationWorkflowService.EdgeCaseTests.cs | 6 +- ...tionWorkflowService.LeaseLifecycleTests.cs | 4 +- .../ApplicationWorkflowServiceTests.cs | 4 +- .../Services/NotificationServiceTests.cs | 617 ++++++++++++++++++ .../LeaseWorkflowService.Tests.cs | 6 +- Aquiis.SimpleStart.UI.Tests/UnitTest1.cs.bak | 244 ------- 6 files changed, 627 insertions(+), 254 deletions(-) create mode 100644 Aquiis.SimpleStart.Tests/Infrastructure/Services/NotificationServiceTests.cs delete mode 100644 Aquiis.SimpleStart.UI.Tests/UnitTest1.cs.bak diff --git a/Aquiis.SimpleStart.Tests/ApplicationWorkflowService.EdgeCaseTests.cs b/Aquiis.SimpleStart.Tests/ApplicationWorkflowService.EdgeCaseTests.cs index c613473..b8755ff 100644 --- a/Aquiis.SimpleStart.Tests/ApplicationWorkflowService.EdgeCaseTests.cs +++ b/Aquiis.SimpleStart.Tests/ApplicationWorkflowService.EdgeCaseTests.cs @@ -33,7 +33,7 @@ private static async Task CreateTestContextAsync() { var connection = new Microsoft.Data.Sqlite.SqliteConnection("Data Source=:memory:"); connection.Open(); - var options = new DbContextOptionsBuilder() + var options = new DbContextOptionsBuilder() .UseSqlite(connection) .Options; @@ -56,7 +56,7 @@ private static async Task CreateTestContextAsync() var serviceProvider = new Mock(); var userContext = new UserContextService(mockAuth.Object, mockUserManager.Object, serviceProvider.Object); - var context = new Infrastructure.Data.ApplicationDbContext(options); + var context = new SimpleStart.Infrastructure.Data.ApplicationDbContext(options); await context.Database.EnsureCreatedAsync(); var appUserEntity = new ApplicationUser { Id = testUserId, UserName = "testuser", Email = "t@t.com", ActiveOrganizationId = orgId }; @@ -146,7 +146,7 @@ private static async Task CreateTestContextAsync() private class TestContext : IAsyncDisposable { public required Microsoft.Data.Sqlite.SqliteConnection Connection { get; init; } - public required Infrastructure.Data.ApplicationDbContext Context { get; init; } + public required Aquiis.SimpleStart.Infrastructure.Data.ApplicationDbContext Context { get; init; } public required ApplicationWorkflowService WorkflowService { get; init; } public required string UserId { get; init; } public required Guid OrgId { get; init; } diff --git a/Aquiis.SimpleStart.Tests/ApplicationWorkflowService.LeaseLifecycleTests.cs b/Aquiis.SimpleStart.Tests/ApplicationWorkflowService.LeaseLifecycleTests.cs index 9df69c2..6d569f0 100644 --- a/Aquiis.SimpleStart.Tests/ApplicationWorkflowService.LeaseLifecycleTests.cs +++ b/Aquiis.SimpleStart.Tests/ApplicationWorkflowService.LeaseLifecycleTests.cs @@ -23,7 +23,7 @@ public async Task GenerateAndAcceptLeaseOffer_CreatesLeaseAndTenant_UpdatesPrope // Arrange - setup SQLite in-memory var connection = new Microsoft.Data.Sqlite.SqliteConnection("Data Source=:memory:"); connection.Open(); - var options = new DbContextOptionsBuilder() + var options = new DbContextOptionsBuilder() .UseSqlite(connection) .Options; @@ -49,7 +49,7 @@ public async Task GenerateAndAcceptLeaseOffer_CreatesLeaseAndTenant_UpdatesPrope var userContext = new UserContextService(mockAuth.Object, mockUserManager.Object, serviceProvider.Object); // Create DbContext and seed data - await using var context = new Infrastructure.Data.ApplicationDbContext(options); + await using var context = new SimpleStart.Infrastructure.Data.ApplicationDbContext(options); await context.Database.EnsureCreatedAsync(); var appUserEntity = new ApplicationUser { Id = testUserId, UserName = "testuser", Email = "t@t.com", ActiveOrganizationId = orgId }; diff --git a/Aquiis.SimpleStart.Tests/ApplicationWorkflowServiceTests.cs b/Aquiis.SimpleStart.Tests/ApplicationWorkflowServiceTests.cs index 2c8a524..b0527f7 100644 --- a/Aquiis.SimpleStart.Tests/ApplicationWorkflowServiceTests.cs +++ b/Aquiis.SimpleStart.Tests/ApplicationWorkflowServiceTests.cs @@ -24,7 +24,7 @@ public async Task GetApplicationWorkflowStateAsync_ReturnsExpectedState() // Use SQLite in-memory to support transactions used by workflow base class var connection = new Microsoft.Data.Sqlite.SqliteConnection("Data Source=:memory:"); connection.Open(); - var options = new DbContextOptionsBuilder() + var options = new DbContextOptionsBuilder() .UseSqlite(connection) .Options; // Create test user and org @@ -52,7 +52,7 @@ public async Task GetApplicationWorkflowStateAsync_ReturnsExpectedState() var userContext = new UserContextService(mockAuth.Object, mockUserManager.Object, serviceProvider.Object); // Create DbContext and seed prospect/property - await using var context = new Infrastructure.Data.ApplicationDbContext(options); + await using var context = new SimpleStart.Infrastructure.Data.ApplicationDbContext(options); // Ensure schema is created for SQLite in-memory await context.Database.EnsureCreatedAsync(); var appUserEntity = new ApplicationUser { Id = testUserId, UserName = "testuser", Email = "t@t.com", ActiveOrganizationId = orgId }; diff --git a/Aquiis.SimpleStart.Tests/Infrastructure/Services/NotificationServiceTests.cs b/Aquiis.SimpleStart.Tests/Infrastructure/Services/NotificationServiceTests.cs new file mode 100644 index 0000000..248329f --- /dev/null +++ b/Aquiis.SimpleStart.Tests/Infrastructure/Services/NotificationServiceTests.cs @@ -0,0 +1,617 @@ + +using System; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; +using Aquiis.SimpleStart.Application.Services; +using Aquiis.SimpleStart.Core.Constants; +using Aquiis.SimpleStart.Core.Entities; +using Aquiis.SimpleStart.Core.Interfaces.Services; +using Aquiis.SimpleStart.Infrastructure.Data; +using Aquiis.SimpleStart.Shared.Components.Account; +using Aquiis.SimpleStart.Shared.Services; +using Microsoft.AspNetCore.Components.Authorization; +using Microsoft.AspNetCore.Identity; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +using Moq; +using Xunit; + +namespace Aquiis.SimpleStart.Tests.Infrastructure.Services +{ + public class NotificationServiceTests : IDisposable + { + private readonly SqliteConnection _connection; + private readonly ApplicationDbContext _context; + private readonly NotificationService _service; + private readonly Mock _mockEmailService; + private readonly Mock _mockSMSService; + private readonly UserContextService _userContext; + private readonly Guid _testOrgId; + private readonly string _testUserId; + + public NotificationServiceTests() + { + // Setup SQLite in-memory database + _connection = new SqliteConnection("Data Source=:memory:"); + _connection.Open(); + + var options = new DbContextOptionsBuilder() + .UseSqlite(_connection) + .Options; + + _context = new ApplicationDbContext(options); + _context.Database.EnsureCreated(); + + // Setup test organization and user + _testUserId = Guid.NewGuid().ToString(); + _testOrgId = Guid.NewGuid(); + + var testUser = new ApplicationUser + { + Id = _testUserId, + UserName = "testuser", + Email = "test@example.com", + ActiveOrganizationId = _testOrgId + }; + + var testOrg = new Organization + { + Id = _testOrgId, + Name = "Test Organization", + OwnerId = _testUserId, + IsActive = true, + CreatedBy = _testUserId, + CreatedOn = DateTime.UtcNow + }; + + _context.Users.Add(testUser); + _context.Organizations.Add(testOrg); + _context.SaveChanges(); + + // Setup UserContextService with mocks + var claims = new ClaimsPrincipal(new ClaimsIdentity(new[] + { + new Claim(ClaimTypes.NameIdentifier, _testUserId) + }, "TestAuth")); + + var mockAuth = new Mock(); + mockAuth.Setup(a => a.GetAuthenticationStateAsync()) + .ReturnsAsync(new AuthenticationState(claims)); + + var mockUserStore = new Mock>(); + var mockUserManager = new Mock>( + mockUserStore.Object, null, null, null, null, null, null, null, null); + mockUserManager.Setup(u => u.FindByIdAsync(It.IsAny())) + .ReturnsAsync(testUser); + + var mockServiceProvider = new Mock(); + _userContext = new UserContextService(mockAuth.Object, mockUserManager.Object, mockServiceProvider.Object); + + // Setup mock email and SMS services + _mockEmailService = new Mock(); + _mockSMSService = new Mock(); + + // Setup ApplicationSettings + var settings = Options.Create(new ApplicationSettings + { + AppName = "Aquiis", + Version = "0.3.0" + }); + + // Create NotificationService + _service = new NotificationService( + _context, + _userContext, + _mockEmailService.Object, + _mockSMSService.Object, + settings, + new NullLogger()); + } + + [Fact] + public async Task SendNotificationAsync_CreatesInAppNotification() + { + // Arrange + var title = "Test Notification"; + var message = "Test message content"; + + // Act + var notification = await _service.SendNotificationAsync( + _testUserId, + title, + message, + NotificationConstants.Types.Info, + NotificationConstants.Categories.System); + + // Assert + Assert.NotNull(notification); + Assert.Equal(title, notification.Title); + Assert.Equal(message, notification.Message); + Assert.Equal(NotificationConstants.Types.Info, notification.Type); + Assert.Equal(NotificationConstants.Categories.System, notification.Category); + Assert.False(notification.IsRead); + Assert.Null(notification.ReadOn); + Assert.True(notification.SendInApp); + Assert.Equal(_testOrgId, notification.OrganizationId); + Assert.Equal(_testUserId, notification.RecipientUserId); + } + + [Fact] + public async Task SendNotificationAsync_WithRelatedEntity_SavesReference() + { + // Arrange + var relatedEntityId = Guid.NewGuid(); + var relatedEntityType = "Lease"; + + // Act + var notification = await _service.SendNotificationAsync( + _testUserId, + "Entity Notification", + "Related to lease", + NotificationConstants.Types.Info, + NotificationConstants.Categories.Lease, + relatedEntityId, + relatedEntityType); + + // Assert + Assert.NotNull(notification); + Assert.Equal(relatedEntityId, notification.RelatedEntityId); + Assert.Equal(relatedEntityType, notification.RelatedEntityType); + } + + [Fact] + public async Task SendNotificationAsync_SendsEmailWhenEnabled() + { + // Arrange + var prefs = new NotificationPreferences + { + Id = Guid.NewGuid(), + OrganizationId = _testOrgId, + UserId = _testUserId, + EnableEmailNotifications = true, + EmailAddress = "test@example.com", + EmailPaymentDue = true, + CreatedBy = _testUserId, + CreatedOn = DateTime.UtcNow + }; + _context.NotificationPreferences.Add(prefs); + await _context.SaveChangesAsync(); + + _mockEmailService.Setup(e => e.SendEmailAsync( + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns(Task.CompletedTask); + + // Act + var notification = await _service.SendNotificationAsync( + _testUserId, + "Payment Due", + "Your rent is due", + NotificationConstants.Types.Warning, + NotificationConstants.Categories.Payment); + + // Assert + Assert.True(notification.SendEmail); + Assert.True(notification.EmailSent); + Assert.NotNull(notification.EmailSentOn); + Assert.Null(notification.EmailError); + _mockEmailService.Verify(e => e.SendEmailAsync( + "test@example.com", + "Payment Due", + "Your rent is due"), Times.Once); + } + + [Fact] + public async Task SendNotificationAsync_DoesNotSendEmailWhenDisabled() + { + // Arrange + var prefs = new NotificationPreferences + { + Id = Guid.NewGuid(), + OrganizationId = _testOrgId, + UserId = _testUserId, + EnableEmailNotifications = false, + CreatedBy = _testUserId, + CreatedOn = DateTime.UtcNow + }; + _context.NotificationPreferences.Add(prefs); + await _context.SaveChangesAsync(); + + // Act + var notification = await _service.SendNotificationAsync( + _testUserId, + "Test", + "Message", + NotificationConstants.Types.Info, + NotificationConstants.Categories.System); + + // Assert + Assert.False(notification.SendEmail); + Assert.False(notification.EmailSent); + _mockEmailService.Verify(e => e.SendEmailAsync( + It.IsAny(), + It.IsAny(), + It.IsAny()), Times.Never); + } + + [Fact] + public async Task SendNotificationAsync_RespectsEmailCategoryPreferences() + { + // Arrange + var prefs = new NotificationPreferences + { + Id = Guid.NewGuid(), + OrganizationId = _testOrgId, + UserId = _testUserId, + EnableEmailNotifications = true, + EmailAddress = "test@example.com", + EmailPaymentDue = false, // Disabled for payment category + EmailLeaseExpiring = true, + CreatedBy = _testUserId, + CreatedOn = DateTime.UtcNow + }; + _context.NotificationPreferences.Add(prefs); + await _context.SaveChangesAsync(); + + // Act + var paymentNotification = await _service.SendNotificationAsync( + _testUserId, + "Payment Due", + "Rent due", + NotificationConstants.Types.Warning, + NotificationConstants.Categories.Payment); + + var leaseNotification = await _service.SendNotificationAsync( + _testUserId, + "Lease Expiring", + "Lease ends soon", + NotificationConstants.Types.Warning, + NotificationConstants.Categories.Lease); + + // Assert + Assert.False(paymentNotification.SendEmail); + Assert.True(leaseNotification.SendEmail); + } + + [Fact] + public async Task SendNotificationAsync_SendsSMSWhenEnabled() + { + // Arrange + var prefs = new NotificationPreferences + { + Id = Guid.NewGuid(), + OrganizationId = _testOrgId, + UserId = _testUserId, + EnableSMSNotifications = true, + PhoneNumber = "+15555551234", + SMSPaymentDue = true, + CreatedBy = _testUserId, + CreatedOn = DateTime.UtcNow + }; + _context.NotificationPreferences.Add(prefs); + await _context.SaveChangesAsync(); + + _mockSMSService.Setup(s => s.SendSMSAsync( + It.IsAny(), + It.IsAny())) + .Returns(Task.CompletedTask); + + // Act + var notification = await _service.SendNotificationAsync( + _testUserId, + "Payment Due", + "Your rent is due", + NotificationConstants.Types.Warning, + NotificationConstants.Categories.Payment); + + // Assert + Assert.True(notification.SendSMS); + Assert.True(notification.SMSSent); + Assert.NotNull(notification.SMSSentOn); + Assert.Null(notification.SMSError); + _mockSMSService.Verify(s => s.SendSMSAsync( + "+15555551234", + "Payment Due: Your rent is due"), Times.Once); + } + + [Fact] + public async Task SendNotificationAsync_HandlesEmailFailureGracefully() + { + // Arrange + var prefs = new NotificationPreferences + { + Id = Guid.NewGuid(), + OrganizationId = _testOrgId, + UserId = _testUserId, + EnableEmailNotifications = true, + EmailAddress = "test@example.com", + EmailPaymentDue = true, + CreatedBy = _testUserId, + CreatedOn = DateTime.UtcNow + }; + _context.NotificationPreferences.Add(prefs); + await _context.SaveChangesAsync(); + + _mockEmailService.Setup(e => e.SendEmailAsync( + It.IsAny(), + It.IsAny(), + It.IsAny())) + .ThrowsAsync(new Exception("Email service unavailable")); + + // Act + var notification = await _service.SendNotificationAsync( + _testUserId, + "Test", + "Message", + NotificationConstants.Types.Info, + NotificationConstants.Categories.Payment); + + // Assert + Assert.NotNull(notification); + Assert.True(notification.SendEmail); + Assert.False(notification.EmailSent); + Assert.Null(notification.EmailSentOn); + Assert.NotNull(notification.EmailError); + Assert.Contains("Email service unavailable", notification.EmailError); + } + + [Fact] + public async Task SendNotificationAsync_HandlesSMSFailureGracefully() + { + // Arrange + var prefs = new NotificationPreferences + { + Id = Guid.NewGuid(), + OrganizationId = _testOrgId, + UserId = _testUserId, + EnableSMSNotifications = true, + PhoneNumber = "+15555551234", + SMSPaymentDue = true, + CreatedBy = _testUserId, + CreatedOn = DateTime.UtcNow + }; + _context.NotificationPreferences.Add(prefs); + await _context.SaveChangesAsync(); + + _mockSMSService.Setup(s => s.SendSMSAsync( + It.IsAny(), + It.IsAny())) + .ThrowsAsync(new Exception("SMS service unavailable")); + + // Act + var notification = await _service.SendNotificationAsync( + _testUserId, + "Test", + "Message", + NotificationConstants.Types.Info, + NotificationConstants.Categories.Payment); + + // Assert + Assert.NotNull(notification); + Assert.True(notification.SendSMS); + Assert.False(notification.SMSSent); + Assert.Null(notification.SMSSentOn); + Assert.NotNull(notification.SMSError); + Assert.Contains("SMS service unavailable", notification.SMSError); + } + + [Fact] + public async Task MarkAsReadAsync_UpdatesNotificationStatus() + { + // Arrange + var notification = await _service.SendNotificationAsync( + _testUserId, + "Test", + "Message", + NotificationConstants.Types.Info, + NotificationConstants.Categories.System); + + Assert.False(notification.IsRead); + Assert.Null(notification.ReadOn); + + // Act + await _service.MarkAsReadAsync(notification.Id); + + // Assert + var updated = await _context.Notifications.FindAsync(notification.Id); + Assert.NotNull(updated); + Assert.True(updated.IsRead); + Assert.NotNull(updated.ReadOn); + } + + [Fact] + public async Task MarkAsReadAsync_HandlesNonExistentNotification() + { + // Arrange + var nonExistentId = Guid.NewGuid(); + + // Act & Assert - should not throw + await _service.MarkAsReadAsync(nonExistentId); + } + + [Fact] + public async Task GetUnreadNotificationsAsync_ReturnsOnlyUnreadForCurrentUser() + { + // Arrange + await _service.SendNotificationAsync( + _testUserId, + "Unread 1", + "Message 1", + NotificationConstants.Types.Info, + NotificationConstants.Categories.System); + + var notification2 = await _service.SendNotificationAsync( + _testUserId, + "Unread 2", + "Message 2", + NotificationConstants.Types.Info, + NotificationConstants.Categories.System); + + await _service.MarkAsReadAsync(notification2.Id); + + // Act + var unread = await _service.GetUnreadNotificationsAsync(); + + // Assert + Assert.Single(unread); + Assert.Equal("Unread 1", unread[0].Title); + Assert.False(unread[0].IsRead); + } + + [Fact] + public async Task GetNotificationHistoryAsync_ReturnsAllNotificationsForCurrentUser() + { + // Arrange + var notification1 = await _service.SendNotificationAsync( + _testUserId, + "Notification 1", + "Message 1", + NotificationConstants.Types.Info, + NotificationConstants.Categories.System); + + var notification2 = await _service.SendNotificationAsync( + _testUserId, + "Notification 2", + "Message 2", + NotificationConstants.Types.Warning, + NotificationConstants.Categories.Payment); + + await _service.MarkAsReadAsync(notification1.Id); + + // Act + var history = await _service.GetNotificationHistoryAsync(100); + + // Assert + Assert.Equal(2, history.Count); + Assert.Contains(history, n => n.Title == "Notification 1" && n.IsRead); + Assert.Contains(history, n => n.Title == "Notification 2" && !n.IsRead); + } + + [Fact] + public async Task GetNotificationHistoryAsync_RespectsCountLimit() + { + // Arrange + for (int i = 0; i < 5; i++) + { + await _service.SendNotificationAsync( + _testUserId, + $"Notification {i}", + $"Message {i}", + NotificationConstants.Types.Info, + NotificationConstants.Categories.System); + } + + // Act + var history = await _service.GetNotificationHistoryAsync(3); + + // Assert + Assert.Equal(3, history.Count); + } + + [Fact] + public async Task Notifications_AreIsolatedByOrganization() + { + // Arrange + var otherOrgId = Guid.NewGuid(); + var otherUserId = "other-user-456"; + + var otherUser = new ApplicationUser + { + Id = otherUserId, + UserName = "otheruser", + Email = "other@example.com", + ActiveOrganizationId = otherOrgId + }; + + var otherOrg = new Organization + { + Id = otherOrgId, + Name = "Other Organization", + OwnerId = otherUserId, + IsActive = true, + CreatedBy = otherUserId, + CreatedOn = DateTime.UtcNow + }; + + _context.Users.Add(otherUser); + _context.Organizations.Add(otherOrg); + await _context.SaveChangesAsync(); + + // Create notification for test user + await _service.SendNotificationAsync( + _testUserId, + "Test User Notification", + "Message for test user", + NotificationConstants.Types.Info, + NotificationConstants.Categories.System); + + // Create notification for other user's org directly + var otherNotification = new Notification + { + Id = Guid.NewGuid(), + OrganizationId = otherOrgId, + RecipientUserId = otherUserId, + Title = "Other User Notification", + Message = "Message for other user", + Type = NotificationConstants.Types.Info, + Category = NotificationConstants.Categories.System, + SentOn = DateTime.UtcNow, + CreatedBy = otherUserId, + CreatedOn = DateTime.UtcNow + }; + _context.Notifications.Add(otherNotification); + await _context.SaveChangesAsync(); + + // Act + var unread = await _service.GetUnreadNotificationsAsync(); + + // Assert + Assert.Single(unread); + Assert.Equal("Test User Notification", unread[0].Title); + Assert.Equal(_testOrgId, unread[0].OrganizationId); + } + + [Fact] + public async Task SendNotificationAsync_CreatesDefaultPreferencesForNewUser() + { + // Arrange + var newUserId = "new-user-789"; + var newUser = new ApplicationUser + { + Id = newUserId, + UserName = "newuser", + Email = "new@example.com", + ActiveOrganizationId = _testOrgId + }; + _context.Users.Add(newUser); + await _context.SaveChangesAsync(); + + // Act + var notification = await _service.SendNotificationAsync( + newUserId, + "First Notification", + "Welcome", + NotificationConstants.Types.Info, + NotificationConstants.Categories.System); + + // Assert + var prefs = await _context.NotificationPreferences + .FirstOrDefaultAsync(p => p.UserId == newUserId && p.OrganizationId == _testOrgId); + + Assert.NotNull(prefs); + Assert.True(prefs.EnableInAppNotifications); + Assert.True(prefs.EnableEmailNotifications); + Assert.False(prefs.EnableSMSNotifications); + } + + public void Dispose() + { + _context?.Dispose(); + _connection?.Dispose(); + } + } +} \ No newline at end of file diff --git a/Aquiis.SimpleStart.Tests/LeaseWorkflowService.Tests.cs b/Aquiis.SimpleStart.Tests/LeaseWorkflowService.Tests.cs index 3f2dd72..a8f9181 100644 --- a/Aquiis.SimpleStart.Tests/LeaseWorkflowService.Tests.cs +++ b/Aquiis.SimpleStart.Tests/LeaseWorkflowService.Tests.cs @@ -38,7 +38,7 @@ private static async Task CreateTestContextAsync() { var connection = new Microsoft.Data.Sqlite.SqliteConnection("Data Source=:memory:"); connection.Open(); - var options = new DbContextOptionsBuilder() + var options = new DbContextOptionsBuilder() .UseSqlite(connection) .Options; @@ -61,7 +61,7 @@ private static async Task CreateTestContextAsync() var serviceProvider = new Mock(); var userContext = new UserContextService(mockAuth.Object, mockUserManager.Object, serviceProvider.Object); - var context = new Infrastructure.Data.ApplicationDbContext(options); + var context = new SimpleStart.Infrastructure.Data.ApplicationDbContext(options); await context.Database.EnsureCreatedAsync(); var appUserEntity = new ApplicationUser { Id = testUserId, UserName = "testuser", Email = "t@t.com", ActiveOrganizationId = orgId }; @@ -176,7 +176,7 @@ private static async Task CreateSecurityDepositAsync( private class TestContext : IAsyncDisposable { public required Microsoft.Data.Sqlite.SqliteConnection Connection { get; init; } - public required Infrastructure.Data.ApplicationDbContext Context { get; init; } + public required Aquiis.SimpleStart.Infrastructure.Data.ApplicationDbContext Context { get; init; } public required LeaseWorkflowService WorkflowService { get; init; } public required string UserId { get; init; } public required Guid OrgId { get; init; } diff --git a/Aquiis.SimpleStart.UI.Tests/UnitTest1.cs.bak b/Aquiis.SimpleStart.UI.Tests/UnitTest1.cs.bak deleted file mode 100644 index e461f17..0000000 --- a/Aquiis.SimpleStart.UI.Tests/UnitTest1.cs.bak +++ /dev/null @@ -1,244 +0,0 @@ -using Microsoft.Playwright; -using Microsoft.Playwright.NUnit; - -namespace Aquiis.Tests; - -/// -/// End-to-end tests for Phase 5.5 Multi-Organization Management scenarios. -/// Based on PROPERTY-TENANT-LIFECYCLE-ROADMAP.md testing scenarios. -/// -[Parallelizable(ParallelScope.Self)] -[TestFixture] -public class PropertyManagementTestExamples : PageTest -{ - private const string BaseUrl = "http://localhost:5197"; - private const int KeepBrowserOpenSeconds = 30; // Set to 0 to close immediately - - public override BrowserNewContextOptions ContextOptions() - { - return new BrowserNewContextOptions - { - IgnoreHTTPSErrors = true, - BaseURL = BaseUrl, - RecordVideoDir = Path.Combine(Directory.GetCurrentDirectory(), "test-videos"), - RecordVideoSize = new RecordVideoSize { Width = 1280, Height = 720 } - }; - } - - /// - /// Test Scenario 1: Owner - Full access across all organizations - /// - [Test] - public async Task Scenario1_Owner_HasFullAccessToAllOrganizations() - { - // Navigate to home page - await Page.GotoAsync(BaseUrl); - - // Click Sign In button to get to login page - await Page.ClickAsync("a[href='/Account/Login']"); - await Page.WaitForSelectorAsync("#Input\\.Email"); - - // Login as Owner - await Page.FillAsync("#Input\\.Email", "owner1@aquiis.com"); - await Page.FillAsync("#Input\\.Password", "Today123"); - await Page.ClickAsync("button[type='submit']"); - - // Wait for dashboard to load - await Page.WaitForSelectorAsync("text=Dashboard"); - - // Verify owner can access Properties page - await Page.ClickAsync("a[href='propertymanagement/properties']"); - await Page.WaitForSelectorAsync("h1:has-text('Properties')"); - - // Verify "Add Property" button is visible (full access) - await Expect(Page.GetByRole(AriaRole.Button, new() { Name = "Add Property" })) - .ToBeVisibleAsync(); - - // Success - owner can access Properties page with full permissions - - // Keep browser open for review/recording - if (KeepBrowserOpenSeconds > 0) - await Task.Delay(KeepBrowserOpenSeconds * 1000); - } - - /// - /// Test Scenario 2: Administrator - Manage single organization - /// - [Test] - public async Task Scenario2_Administrator_ManagesSingleOrganization() - { - await Page.GotoAsync(BaseUrl); - - // Click Sign In button - await Page.ClickAsync("a[href='/Account/Login']"); - await Page.WaitForSelectorAsync("#Input\\.Email"); - - // Login as Administrator - await Page.FillAsync("#Input\\.Email", "jc@example.com"); - await Page.FillAsync("#Input\\.Password", "Today123"); - await Page.ClickAsync("button[type='submit']"); - - await Page.WaitForSelectorAsync("text=Dashboard"); - - // Verify Alice can create properties - await Page.ClickAsync("a[href='propertymanagement/properties']"); - await Page.WaitForSelectorAsync("h1:has-text('Properties')"); - - await Expect(Page.GetByRole(AriaRole.Button, new() { Name = "Add Property" })) - .ToBeVisibleAsync(); - - // Verify Alice can access Tenants - await Page.ClickAsync("a[href='propertymanagement/tenants']"); - await Page.WaitForSelectorAsync("h1:has-text('Tenants')"); - - // Verify "Add Tenant" button is visible - await Expect(Page.GetByRole(AriaRole.Button, new() { Name = "Add Tenant" })) - .ToBeVisibleAsync(); - - // Keep browser open for review/recording - if (KeepBrowserOpenSeconds > 0) - await Task.Delay(KeepBrowserOpenSeconds * 1000); - } - - /// - /// Test Scenario 3: PropertyManager - View/edit assigned properties only - /// - [Test] - public async Task Scenario3_PropertyManager_HasLimitedAccess() - { - await Page.GotoAsync(BaseUrl); - - // Click Sign In button - await Page.ClickAsync("a[href='/Account/Login']"); - await Page.WaitForSelectorAsync("#Input\\.Email"); - - // Login as PropertyManager - await Page.FillAsync("#Input\\.Email", "jh@example.com"); - await Page.FillAsync("#Input\\.Password", "Today123"); - await Page.ClickAsync("button[type='submit']"); - - await Page.WaitForSelectorAsync("text=Dashboard"); - - // Navigate to Properties - await Page.ClickAsync("a[href='propertymanagement/properties']"); - await Page.WaitForSelectorAsync("h1:has-text('Properties')"); - - // Verify Bob can see "Add Property" button (PropertyManager can create) - await Expect(Page.GetByRole(AriaRole.Button, new() { Name = "Add Property" })) - .ToBeVisibleAsync(); - - // Success - PropertyManager can access Properties page with create permissions - - // Keep browser open for review/recording - if (KeepBrowserOpenSeconds > 0) - await Task.Delay(KeepBrowserOpenSeconds * 1000); - } - - /// - /// Test Scenario 4: User - Read-only access - /// - [Test] - public async Task Scenario4_User_HasReadOnlyAccess() - { - await Page.GotoAsync(BaseUrl); - - // Click Sign In button - await Page.ClickAsync("a[href='/Account/Login']"); - await Page.WaitForSelectorAsync("#Input\\.Email"); - - // Login as User - await Page.FillAsync("#Input\\.Email", "mya@example.com"); - await Page.FillAsync("#Input\\.Password", "Today123"); - await Page.ClickAsync("button[type='submit']"); - - await Page.WaitForSelectorAsync("text=Dashboard"); - - // Verify Lisa can see Dashboard - await Expect(Page.Locator("h1, h2").Filter(new() { HasText = "Dashboard" })) - .ToBeVisibleAsync(); - - // Navigate to Properties - await Page.ClickAsync("a[href='propertymanagement/properties']"); - await Page.WaitForSelectorAsync("h1:has-text('Properties')"); - - // Verify "Add Property" button is NOT visible (read-only) - await Expect(Page.GetByRole(AriaRole.Button, new() { Name = "Add Property" })) - .Not.ToBeVisibleAsync(); - - // Success - user can view Properties page (read-only) - - // Navigate to Tenants - await Page.ClickAsync("a[href='propertymanagement/tenants']"); - await Page.WaitForSelectorAsync("h1:has-text('Tenants')"); - - // Verify "Add Tenant" button is NOT visible (read-only) - await Expect(Page.GetByRole(AriaRole.Button, new() { Name = "Add Tenant" })) - .Not.ToBeVisibleAsync(); - - // Keep browser open for review/recording - if (KeepBrowserOpenSeconds > 0) - await Task.Delay(KeepBrowserOpenSeconds * 1000); - } - - /// - /// Test Scenario 5: Cross-organization isolation - /// - [Test] - public async Task Scenario5_UsersOnlySeeTheirOrganizationData() - { - await Page.GotoAsync(BaseUrl); - - // Click Sign In button - await Page.ClickAsync("a[href='/Account/Login']"); - await Page.WaitForSelectorAsync("#Input\\.Email"); - - // Login as Administrator (single org access) - await Page.FillAsync("#Input\\.Email", "jc@example.com"); - await Page.FillAsync("#Input\\.Password", "Today123"); - await Page.ClickAsync("button[type='submit']"); - - await Page.WaitForSelectorAsync("text=Dashboard"); - - // Navigate to Properties - await Page.ClickAsync("a[href='propertymanagement/properties']"); - await Page.WaitForSelectorAsync("h1:has-text('Properties')"); - - // Verify page loaded successfully - organization isolation is enforced at service layer - // This test confirms Administrator can access the Properties page - // Data isolation is tested through the service layer (not UI) - var pageTitle = await Page.Locator("h1:has-text('Properties')").TextContentAsync(); - Assert.That(pageTitle, Does.Contain("Properties"), "Administrator can access Properties page"); - - // Keep browser open for review/recording - if (KeepBrowserOpenSeconds > 0) - await Task.Delay(KeepBrowserOpenSeconds * 1000); - } - - /// - /// Test Scenario 6: Owner can access system across organizations - /// - [Test] - public async Task Scenario6_Owner_CanAccessSystem() - { - await Page.GotoAsync(BaseUrl); - - // Click Sign In button - await Page.ClickAsync("a[href='/Account/Login']"); - await Page.WaitForSelectorAsync("#Input\\.Email"); - - // Login as Owner (access to multiple orgs) - await Page.FillAsync("#Input\\.Email", "owner1@aquiis.com"); - await Page.FillAsync("#Input\\.Password", "Today123"); - await Page.ClickAsync("button[type='submit']"); - - await Page.WaitForSelectorAsync("text=Dashboard"); - - // Verify owner can access Properties - await Page.ClickAsync("a[href='propertymanagement/properties']"); - await Page.WaitForSelectorAsync("h1:has-text('Properties')"); - - // Keep browser open for review/recording - if (KeepBrowserOpenSeconds > 0) - await Task.Delay(KeepBrowserOpenSeconds * 1000); - } -}