using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; using ROLAC.API.Entities; namespace ROLAC.API.Data; public class AppDbContext : IdentityDbContext { public AppDbContext(DbContextOptions options) : base(options) { } public DbSet RefreshTokens => Set(); public DbSet Members => Set(); public DbSet FamilyUnits => Set(); public DbSet GivingCategories => Set(); public DbSet OfferingSessions => Set(); public DbSet Givings => Set(); public DbSet Ministries => Set(); public DbSet ExpenseCategoryGroups => Set(); public DbSet ExpenseSubCategories => Set(); public DbSet Expenses => Set(); public DbSet MonthlyStatements => Set(); public DbSet ChurchProfiles => Set(); public DbSet Checks => Set(); public DbSet CheckLines => Set(); public DbSet MealAttendances => Set(); protected override void OnModelCreating(ModelBuilder builder) { base.OnModelCreating(builder); // ── RefreshToken (unchanged) ──────────────────────────────────────── builder.Entity(entity => { entity.HasKey(e => e.Id); entity.HasIndex(e => e.TokenHash).IsUnique(); entity.Property(e => e.TokenHash).HasMaxLength(64).IsRequired(); entity.Property(e => e.UserId).HasMaxLength(450).IsRequired(); entity.Property(e => e.DeviceInfo).HasMaxLength(200); entity.Property(e => e.IpAddress).HasMaxLength(45); entity.Property(e => e.ReplacedByHash).HasMaxLength(64); entity.HasOne(e => e.User).WithMany(u => u.RefreshTokens) .HasForeignKey(e => e.UserId).OnDelete(DeleteBehavior.Cascade); entity.Ignore(e => e.IsExpired); entity.Ignore(e => e.IsRevoked); entity.Ignore(e => e.IsActive); }); // ── AppUser (unchanged + new unique index on MemberId) ────────────── builder.Entity(entity => { entity.Property(e => e.LanguagePreference).HasMaxLength(10).HasDefaultValue("en"); // Nullable unique: one member ↔ one user account, but member can have no account entity.HasIndex(e => e.MemberId).IsUnique() .HasFilter("\"MemberId\" IS NOT NULL"); }); // ── AppRole (unchanged) ───────────────────────────────────────────── builder.Entity(entity => { entity.Property(e => e.Description).HasMaxLength(500); }); // ── FamilyUnit ────────────────────────────────────────────────────── builder.Entity(entity => { entity.Property(e => e.FamilyName_en).HasMaxLength(200); entity.Property(e => e.FamilyName_zh).HasMaxLength(200); }); // ── Member ────────────────────────────────────────────────────────── builder.Entity(entity => { entity.HasQueryFilter(m => !m.IsDeleted); entity.Property(e => e.FirstName_en).HasMaxLength(100).IsRequired(); entity.Property(e => e.LastName_en).HasMaxLength(100).IsRequired(); entity.Property(e => e.NickName).HasMaxLength(100); entity.Property(e => e.FirstName_zh).HasMaxLength(100); entity.Property(e => e.LastName_zh).HasMaxLength(100); entity.Property(e => e.Gender).HasMaxLength(10); entity.Property(e => e.BaptismChurch).HasMaxLength(200); entity.Property(e => e.Email).HasMaxLength(200); entity.Property(e => e.PhoneCell).HasMaxLength(30); entity.Property(e => e.PhoneHome).HasMaxLength(30); entity.Property(e => e.Address).HasMaxLength(500); entity.Property(e => e.City).HasMaxLength(100); entity.Property(e => e.State).HasMaxLength(50); entity.Property(e => e.ZipCode).HasMaxLength(20); entity.Property(e => e.Country).HasMaxLength(100).HasDefaultValue("USA"); entity.Property(e => e.PhotoBlobPath).HasMaxLength(500); entity.Property(e => e.Status).HasMaxLength(20).HasDefaultValue("Member"); entity.Property(e => e.LanguagePreference).HasMaxLength(10).HasDefaultValue("en"); entity.Property(e => e.CreatedBy).HasMaxLength(450); entity.Property(e => e.UpdatedBy).HasMaxLength(450); entity.Property(e => e.DeletedBy).HasMaxLength(450); entity.HasIndex(e => e.Status).HasFilter("\"IsDeleted\" = false"); entity.HasIndex(e => e.Email).HasFilter("\"Email\" IS NOT NULL"); entity.HasIndex(e => e.FamilyUnitId); entity.HasOne(e => e.FamilyUnit).WithMany() .HasForeignKey(e => e.FamilyUnitId).OnDelete(DeleteBehavior.SetNull); }); // ── GivingCategory ─────────────────────────────────────────────────── builder.Entity(entity => { entity.Property(e => e.Name_en).HasMaxLength(200).IsRequired(); entity.Property(e => e.Name_zh).HasMaxLength(200); entity.Property(e => e.Description_en).HasMaxLength(500); entity.Property(e => e.Description_zh).HasMaxLength(500); entity.Property(e => e.CreatedBy).HasMaxLength(450); entity.Property(e => e.UpdatedBy).HasMaxLength(450); }); // ── OfferingSession ────────────────────────────────────────────────── builder.Entity(entity => { entity.Property(e => e.Status).HasMaxLength(20).HasDefaultValue("Draft"); entity.Property(e => e.CashTotal).HasColumnType("decimal(18,2)"); entity.Property(e => e.CheckTotal).HasColumnType("decimal(18,2)"); entity.Property(e => e.SystemTotal).HasColumnType("decimal(18,2)"); entity.Property(e => e.Difference).HasColumnType("decimal(18,2)"); entity.Property(e => e.SubmittedBy).HasMaxLength(450); entity.Property(e => e.ReconciledBy).HasMaxLength(450); entity.Property(e => e.ProofPdfPath).HasMaxLength(500); entity.Property(e => e.CreatedBy).HasMaxLength(450); entity.Property(e => e.UpdatedBy).HasMaxLength(450); entity.HasIndex(e => e.SessionDate).IsUnique(); }); // ── Giving ─────────────────────────────────────────────────────────── builder.Entity(entity => { entity.Property(e => e.Amount).HasColumnType("decimal(18,2)"); entity.Property(e => e.PaymentMethod).HasMaxLength(20).IsRequired(); entity.Property(e => e.CheckNumber).HasMaxLength(50); entity.Property(e => e.ZelleReferenceCode).HasMaxLength(100); entity.Property(e => e.PayPalTransactionId).HasMaxLength(100); entity.Property(e => e.Notes).HasMaxLength(500); entity.Property(e => e.CreatedBy).HasMaxLength(450); entity.Property(e => e.UpdatedBy).HasMaxLength(450); entity.HasIndex(e => new { e.MemberId, e.GivingDate }); entity.HasIndex(e => e.OfferingSessionId).HasFilter("\"OfferingSessionId\" IS NOT NULL"); entity.HasIndex(e => e.GivingDate); entity.HasOne(e => e.GivingCategory).WithMany() .HasForeignKey(e => e.GivingCategoryId).OnDelete(DeleteBehavior.Restrict); entity.HasOne(e => e.Member).WithMany() .HasForeignKey(e => e.MemberId).OnDelete(DeleteBehavior.SetNull); entity.HasOne(e => e.OfferingSession).WithMany(s => s.Givings) .HasForeignKey(e => e.OfferingSessionId).OnDelete(DeleteBehavior.Cascade); }); // ── Ministry ───────────────────────────────────────────────────────── builder.Entity(entity => { entity.Property(e => e.Name_en).HasMaxLength(200).IsRequired(); entity.Property(e => e.Name_zh).HasMaxLength(200); }); // ── ExpenseCategoryGroup ───────────────────────────────────────────── builder.Entity(entity => { entity.Property(e => e.Name_en).HasMaxLength(200).IsRequired(); entity.Property(e => e.Name_zh).HasMaxLength(200); entity.Property(e => e.CreatedBy).HasMaxLength(450); entity.Property(e => e.UpdatedBy).HasMaxLength(450); }); // ── ExpenseSubCategory ─────────────────────────────────────────────── builder.Entity(entity => { entity.Property(e => e.Name_en).HasMaxLength(200).IsRequired(); entity.Property(e => e.Name_zh).HasMaxLength(200); entity.Property(e => e.CreatedBy).HasMaxLength(450); entity.Property(e => e.UpdatedBy).HasMaxLength(450); entity.HasOne(e => e.Group).WithMany(g => g.SubCategories) .HasForeignKey(e => e.GroupId).OnDelete(DeleteBehavior.Restrict); }); // ── Expense ────────────────────────────────────────────────────────── builder.Entity(entity => { entity.HasQueryFilter(e => !e.IsDeleted); entity.Property(e => e.Type).HasMaxLength(30).IsRequired(); entity.Property(e => e.Status).HasMaxLength(30).HasDefaultValue("Draft"); entity.Property(e => e.Amount).HasColumnType("decimal(18,2)"); entity.Property(e => e.Description).HasMaxLength(500).IsRequired(); entity.Property(e => e.VendorName).HasMaxLength(200); entity.Property(e => e.CheckNumber).HasMaxLength(50); entity.Property(e => e.ReceiptBlobPath).HasMaxLength(500); entity.Property(e => e.ReviewNotes).HasMaxLength(500); entity.Property(e => e.SubmittedBy).HasMaxLength(450); entity.Property(e => e.ReviewedBy).HasMaxLength(450); entity.Property(e => e.PaidBy).HasMaxLength(450); entity.Property(e => e.CreatedBy).HasMaxLength(450); entity.Property(e => e.UpdatedBy).HasMaxLength(450); entity.Property(e => e.DeletedBy).HasMaxLength(450); entity.HasIndex(e => e.MinistryId); entity.HasIndex(e => e.Status).HasFilter("\"IsDeleted\" = false"); entity.HasIndex(e => e.ExpenseDate); entity.HasOne(e => e.Ministry).WithMany() .HasForeignKey(e => e.MinistryId).OnDelete(DeleteBehavior.Restrict); entity.HasOne(e => e.CategoryGroup).WithMany() .HasForeignKey(e => e.CategoryGroupId).OnDelete(DeleteBehavior.Restrict); entity.HasOne(e => e.SubCategory).WithMany() .HasForeignKey(e => e.SubCategoryId).OnDelete(DeleteBehavior.Restrict); entity.HasOne(e => e.Member).WithMany() .HasForeignKey(e => e.MemberId).OnDelete(DeleteBehavior.SetNull); }); // ── ChurchProfile (singleton settings) ─────────────────────────────── builder.Entity(entity => { entity.Property(e => e.Name).HasMaxLength(200).IsRequired(); entity.Property(e => e.Address).HasMaxLength(500); entity.Property(e => e.City).HasMaxLength(100); entity.Property(e => e.State).HasMaxLength(50); entity.Property(e => e.ZipCode).HasMaxLength(20); entity.Property(e => e.BankName).HasMaxLength(200); entity.Property(e => e.BankAccountNumber).HasMaxLength(50); entity.Property(e => e.BankRoutingNumber).HasMaxLength(50); entity.Property(e => e.CreatedBy).HasMaxLength(450); entity.Property(e => e.UpdatedBy).HasMaxLength(450); // Optimistic-concurrency token for safe check-number allocation. entity.Property(e => e.xmin).IsRowVersion(); }); // ── Check (disbursement) ───────────────────────────────────────────── builder.Entity(entity => { entity.HasQueryFilter(c => !c.IsDeleted); entity.Property(e => e.CheckNumber).HasMaxLength(50).IsRequired(); entity.Property(e => e.Amount).HasColumnType("decimal(18,2)"); entity.Property(e => e.PayeeType).HasMaxLength(20).IsRequired(); entity.Property(e => e.PayeeName).HasMaxLength(200).IsRequired(); entity.Property(e => e.PayeeAddress).HasMaxLength(500); entity.Property(e => e.PayeeCity).HasMaxLength(100); entity.Property(e => e.PayeeState).HasMaxLength(50); entity.Property(e => e.PayeeZip).HasMaxLength(20); entity.Property(e => e.Status).HasMaxLength(20).HasDefaultValue("Issued"); entity.Property(e => e.Memo).HasMaxLength(500); entity.Property(e => e.IssuedBy).HasMaxLength(450).IsRequired(); entity.Property(e => e.VoidReason).HasMaxLength(500); entity.Property(e => e.VoidedBy).HasMaxLength(450); entity.Property(e => e.ReceiptSignatureBlobPath).HasMaxLength(500); entity.Property(e => e.ReceiptSignedName).HasMaxLength(200); entity.Property(e => e.ReceiptCapturedBy).HasMaxLength(450); entity.Property(e => e.CreatedBy).HasMaxLength(450); entity.Property(e => e.UpdatedBy).HasMaxLength(450); entity.Property(e => e.DeletedBy).HasMaxLength(450); // Unique check number among non-deleted rows. entity.HasIndex(e => e.CheckNumber).IsUnique().HasFilter("\"IsDeleted\" = false"); entity.HasIndex(e => e.Status).HasFilter("\"IsDeleted\" = false"); entity.HasIndex(e => e.CheckDate); entity.HasOne(e => e.Member).WithMany() .HasForeignKey(e => e.MemberId).OnDelete(DeleteBehavior.SetNull); }); // ── CheckLine ──────────────────────────────────────────────────────── builder.Entity(entity => { // Mirror the parent Check's soft-delete filter (required relationship). entity.HasQueryFilter(l => !l.Check!.IsDeleted); entity.Property(e => e.Amount).HasColumnType("decimal(18,2)"); entity.Property(e => e.Description).HasMaxLength(500).IsRequired(); entity.Property(e => e.CreatedBy).HasMaxLength(450); entity.Property(e => e.UpdatedBy).HasMaxLength(450); entity.HasIndex(e => e.CheckId); entity.HasIndex(e => e.ExpenseId); entity.HasOne(e => e.Check).WithMany(c => c.Lines) .HasForeignKey(e => e.CheckId).OnDelete(DeleteBehavior.Cascade); entity.HasOne(e => e.Expense).WithMany() .HasForeignKey(e => e.ExpenseId).OnDelete(DeleteBehavior.Restrict); }); // ── MealAttendance (one shared row per Sunday) ─────────────────────── builder.Entity(entity => { entity.Property(e => e.AdultCount).HasDefaultValue(0); entity.Property(e => e.YouthCount).HasDefaultValue(0); entity.Property(e => e.KidCount).HasDefaultValue(0); entity.Property(e => e.CreatedBy).HasMaxLength(450); entity.Property(e => e.UpdatedBy).HasMaxLength(450); entity.HasIndex(e => e.AttendanceDate).IsUnique(); }); // ── MonthlyStatement ───────────────────────────────────────────────── builder.Entity(entity => { entity.Property(e => e.OpeningBalance).HasColumnType("decimal(18,2)"); entity.Property(e => e.TotalGiving).HasColumnType("decimal(18,2)"); entity.Property(e => e.TotalOtherIncome).HasColumnType("decimal(18,2)"); entity.Property(e => e.TotalExpenses).HasColumnType("decimal(18,2)"); entity.Property(e => e.CalculatedClosingBalance).HasColumnType("decimal(18,2)"); entity.Property(e => e.BankStatementBalance).HasColumnType("decimal(18,2)"); entity.Property(e => e.Difference).HasColumnType("decimal(18,2)"); entity.Property(e => e.FinalizedBy).HasMaxLength(450); entity.Property(e => e.CreatedBy).HasMaxLength(450); entity.Property(e => e.UpdatedBy).HasMaxLength(450); entity.HasIndex(e => new { e.Year, e.Month }).IsUnique(); }); } }