diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 87747a7a4..2c89a87c6 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -44,7 +44,7 @@ jobs:
# context since the per-section split (nobodies-collective/Humans#858).
run: |
dotnet tool install --global dotnet-ef --version 10.0.*
- for ctx in HumansDbContext SystemSettingsDbContext ContainersDbContext AgentDbContext ExpensesDbContext; do
+ for ctx in HumansDbContext SystemSettingsDbContext ContainersDbContext AgentDbContext ExpensesDbContext FinanceDbContext; do
dotnet ef migrations has-pending-model-changes \
--context "$ctx" \
--project src/Humans.Infrastructure \
@@ -174,7 +174,7 @@ jobs:
- name: Apply per-section baselines from scratch
run: |
set -euo pipefail
- for ctx in SystemSettingsDbContext ContainersDbContext AgentDbContext ExpensesDbContext; do
+ for ctx in SystemSettingsDbContext ContainersDbContext AgentDbContext ExpensesDbContext FinanceDbContext; do
db="humans_$(echo "$ctx" | tr '[:upper:]' '[:lower:]')"
docker exec "${{ steps.pg.outputs.container }}" \
psql -U humans -d postgres -c "CREATE DATABASE $db"
@@ -193,7 +193,7 @@ jobs:
# Belt-and-suspenders: Layer 1 already runs this in the build job,
# but re-checking after a real apply guards against any state the
# apply step might have observed differently from a static check.
- for ctx in HumansDbContext SystemSettingsDbContext ContainersDbContext AgentDbContext ExpensesDbContext; do
+ for ctx in HumansDbContext SystemSettingsDbContext ContainersDbContext AgentDbContext ExpensesDbContext FinanceDbContext; do
dotnet ef migrations has-pending-model-changes \
--context "$ctx" \
--project src/Humans.Infrastructure \
diff --git a/src/Humans.Infrastructure/Data/FinanceDbContext.cs b/src/Humans.Infrastructure/Data/FinanceDbContext.cs
new file mode 100644
index 000000000..b13ea5380
--- /dev/null
+++ b/src/Humans.Infrastructure/Data/FinanceDbContext.cs
@@ -0,0 +1,40 @@
+using Humans.Domain.Entities;
+using Humans.Infrastructure.Data.Configurations.Finance;
+using Microsoft.EntityFrameworkCore;
+
+namespace Humans.Infrastructure.Data;
+
+///
+/// Per-section database context for the Finance section
+/// (nobodies-collective/Humans#858): maps only holded_expense_docs,
+/// holded_category_map, holded_ledger_lines,
+/// holded_creditor_contacts and holded_sync_states, with its own
+/// __EFMigrationsHistory_Finance table and migrations under
+/// Migrations/Finance/. Same database, same connection — the split
+/// is a code-side partition of the EF model.
+///
+///
+/// Internal-sealed like (issue #750): repositories
+/// are the only consumers. Configurations are applied explicitly (not by
+/// assembly scanning) so this model can never accrete another section's tables.
+///
+internal sealed class FinanceDbContext(DbContextOptions options)
+ : DbContext(options)
+{
+ public DbSet HoldedExpenseDocs => Set();
+ public DbSet HoldedCategoryMap => Set();
+ public DbSet HoldedLedgerLines => Set();
+ public DbSet HoldedCreditorContacts => Set();
+ public DbSet HoldedSyncStates => Set();
+
+ protected override void OnModelCreating(ModelBuilder builder)
+ {
+ base.OnModelCreating(builder);
+
+ builder.ApplyConfiguration(new HoldedExpenseDocConfiguration());
+ builder.ApplyConfiguration(new HoldedCategoryMapConfiguration());
+ builder.ApplyConfiguration(new HoldedLedgerLineConfiguration());
+ builder.ApplyConfiguration(new HoldedCreditorContactConfiguration());
+ builder.ApplyConfiguration(new HoldedSyncStateConfiguration());
+ }
+}
diff --git a/src/Humans.Infrastructure/Data/FinanceDbContextFactory.cs b/src/Humans.Infrastructure/Data/FinanceDbContextFactory.cs
new file mode 100644
index 000000000..b9330967e
--- /dev/null
+++ b/src/Humans.Infrastructure/Data/FinanceDbContextFactory.cs
@@ -0,0 +1,33 @@
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Design;
+
+namespace Humans.Infrastructure.Data;
+
+///
+/// Design-time factory used by dotnet ef … --context FinanceDbContext.
+/// Mirrors ; the migrations-history table must
+/// match the runtime registration so CI's from-scratch apply records baselines in
+/// __EFMigrationsHistory_Finance.
+///
+internal sealed class FinanceDbContextFactory : IDesignTimeDbContextFactory
+{
+ public FinanceDbContext CreateDbContext(string[] args)
+ {
+ var connectionString =
+ Environment.GetEnvironmentVariable("ConnectionStrings__DefaultConnection")
+ ?? "Host=localhost;Database=humans_design_time;Username=humans;Password=humans";
+
+ var optionsBuilder = new DbContextOptionsBuilder();
+ optionsBuilder.UseNpgsql(
+ connectionString,
+ npgsqlOptions =>
+ {
+ npgsqlOptions.UseNodaTime();
+ npgsqlOptions.MigrationsAssembly("Humans.Infrastructure");
+ npgsqlOptions.MigrationsHistoryTable("__EFMigrationsHistory_Finance");
+ npgsqlOptions.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery);
+ });
+
+ return new FinanceDbContext(optionsBuilder.Options);
+ }
+}
diff --git a/src/Humans.Infrastructure/Data/HumansDbContext.cs b/src/Humans.Infrastructure/Data/HumansDbContext.cs
index a9c9f7d17..44fd4000b 100644
--- a/src/Humans.Infrastructure/Data/HumansDbContext.cs
+++ b/src/Humans.Infrastructure/Data/HumansDbContext.cs
@@ -106,11 +106,6 @@ internal sealed class HumansDbContext(DbContextOptions options)
public DbSet StorePayments => Set();
public DbSet StoreInvoices => Set();
public DbSet StoreTreasurySyncStates => Set();
- public DbSet HoldedExpenseDocs => Set();
- public DbSet HoldedCategoryMap => Set();
- public DbSet HoldedSyncStates => Set();
- public DbSet HoldedLedgerLines => Set();
- public DbSet HoldedCreditorContacts => Set();
// Survey section
public DbSet Surveys => Set();
@@ -133,6 +128,7 @@ internal sealed class HumansDbContext(DbContextOptions options)
typeof(Configurations.Containers.ContainerConfiguration).Namespace!,
typeof(Configurations.Agent.AgentConversationConfiguration).Namespace!,
typeof(Configurations.Expenses.ExpenseReportConfiguration).Namespace!,
+ typeof(Configurations.Finance.HoldedExpenseDocConfiguration).Namespace!,
];
protected override void OnModelCreating(ModelBuilder builder)
diff --git a/src/Humans.Infrastructure/Hosting/InfrastructureServiceCollectionExtensions.cs b/src/Humans.Infrastructure/Hosting/InfrastructureServiceCollectionExtensions.cs
index 3aba870dc..64eff01d8 100644
--- a/src/Humans.Infrastructure/Hosting/InfrastructureServiceCollectionExtensions.cs
+++ b/src/Humans.Infrastructure/Hosting/InfrastructureServiceCollectionExtensions.cs
@@ -50,6 +50,7 @@ public static IServiceCollection AddHumansPersistence(
services.AddSectionDbContext(sentinelTable: "containers");
services.AddSectionDbContext(sentinelTable: "agent_conversations");
services.AddSectionDbContext(sentinelTable: "expense_reports");
+ services.AddSectionDbContext(sentinelTable: "holded_expense_docs");
services.AddHostedService();
diff --git a/src/Humans.Infrastructure/Migrations/20260715103734_PeelFinance.Designer.cs b/src/Humans.Infrastructure/Migrations/20260715103734_PeelFinance.Designer.cs
new file mode 100644
index 000000000..21492221e
--- /dev/null
+++ b/src/Humans.Infrastructure/Migrations/20260715103734_PeelFinance.Designer.cs
@@ -0,0 +1,6540 @@
+//
+using System;
+using Humans.Infrastructure.Data;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using NodaTime;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+#nullable disable
+
+namespace Humans.Infrastructure.Migrations
+{
+ [DbContext(typeof(HumansDbContext))]
+ [Migration("20260715103734_PeelFinance")]
+ partial class PeelFinance
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "10.0.9")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("Humans.Domain.Entities.AccountMergeRequest", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("AdminNotes")
+ .HasMaxLength(4000)
+ .HasColumnType("character varying(4000)");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Email")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.Property("PendingEmailId")
+ .HasColumnType("uuid");
+
+ b.Property("ResolvedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("ResolvedByUserId")
+ .HasColumnType("uuid");
+
+ b.Property("SourceUserId")
+ .HasColumnType("uuid");
+
+ b.Property("Status")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("character varying(50)");
+
+ b.Property("TargetUserId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ResolvedByUserId");
+
+ b.HasIndex("SourceUserId");
+
+ b.HasIndex("Status");
+
+ b.HasIndex("TargetUserId");
+
+ b.ToTable("account_merge_requests", (string)null);
+ });
+
+ modelBuilder.Entity("Humans.Domain.Entities.Application", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("AdditionalInfo")
+ .HasMaxLength(4000)
+ .HasColumnType("character varying(4000)");
+
+ b.Property("BoardMeetingDate")
+ .HasColumnType("date");
+
+ b.Property("DecisionNote")
+ .HasMaxLength(4000)
+ .HasColumnType("character varying(4000)");
+
+ b.Property("Language")
+ .HasMaxLength(10)
+ .HasColumnType("character varying(10)");
+
+ b.Property("MembershipTier")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Motivation")
+ .IsRequired()
+ .HasMaxLength(4000)
+ .HasColumnType("character varying(4000)");
+
+ b.Property("RenewalReminderSentAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("ResolvedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("ReviewNotes")
+ .HasMaxLength(4000)
+ .HasColumnType("character varying(4000)");
+
+ b.Property("ReviewStartedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("ReviewedByUserId")
+ .HasColumnType("uuid");
+
+ b.Property("RoleUnderstanding")
+ .HasColumnType("text");
+
+ b.Property("SignificantContribution")
+ .HasColumnType("text");
+
+ b.Property("Status")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("SubmittedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("TermExpiresAt")
+ .HasColumnType("date");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("MembershipTier");
+
+ b.HasIndex("ReviewedByUserId");
+
+ b.HasIndex("Status");
+
+ b.HasIndex("SubmittedAt");
+
+ b.HasIndex("UserId");
+
+ b.HasIndex("UserId", "Status");
+
+ b.ToTable("applications", (string)null);
+ });
+
+ modelBuilder.Entity("Humans.Domain.Entities.ApplicationStateHistory", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("ApplicationId")
+ .HasColumnType("uuid");
+
+ b.Property("ChangedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("ChangedByUserId")
+ .HasColumnType("uuid");
+
+ b.Property("Notes")
+ .HasMaxLength(4000)
+ .HasColumnType("character varying(4000)");
+
+ b.Property("Status")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ApplicationId");
+
+ b.HasIndex("ChangedAt");
+
+ b.HasIndex("ChangedByUserId");
+
+ b.ToTable("application_state_history", (string)null);
+ });
+
+ modelBuilder.Entity("Humans.Domain.Entities.AuditLogEntry", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Action")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("character varying(100)");
+
+ b.Property("ActorUserId")
+ .HasColumnType("uuid");
+
+ b.Property("Description")
+ .IsRequired()
+ .HasMaxLength(4000)
+ .HasColumnType("character varying(4000)");
+
+ b.Property("EntityId")
+ .HasColumnType("uuid");
+
+ b.Property("EntityType")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("character varying(100)");
+
+ b.Property("ErrorMessage")
+ .HasMaxLength(4000)
+ .HasColumnType("character varying(4000)");
+
+ b.Property("OccurredAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("RelatedEntityId")
+ .HasColumnType("uuid");
+
+ b.Property("RelatedEntityType")
+ .HasMaxLength(100)
+ .HasColumnType("character varying(100)");
+
+ b.Property("ResourceId")
+ .HasColumnType("uuid");
+
+ b.Property("Role")
+ .HasMaxLength(100)
+ .HasColumnType("character varying(100)");
+
+ b.Property("Success")
+ .HasColumnType("boolean");
+
+ b.Property("SyncSource")
+ .HasMaxLength(100)
+ .HasColumnType("character varying(100)");
+
+ b.Property("UserEmail")
+ .HasMaxLength(500)
+ .HasColumnType("character varying(500)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Action");
+
+ b.HasIndex("ActorUserId");
+
+ b.HasIndex("OccurredAt");
+
+ b.HasIndex("ResourceId");
+
+ b.HasIndex("EntityType", "EntityId");
+
+ b.HasIndex("RelatedEntityType", "RelatedEntityId");
+
+ b.ToTable("audit_log", (string)null);
+ });
+
+ modelBuilder.Entity("Humans.Domain.Entities.BoardVote", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("ApplicationId")
+ .HasColumnType("uuid");
+
+ b.Property("BoardMemberUserId")
+ .HasColumnType("uuid");
+
+ b.Property("Note")
+ .HasMaxLength(4000)
+ .HasColumnType("character varying(4000)");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Vote")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("VotedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ApplicationId");
+
+ b.HasIndex("BoardMemberUserId");
+
+ b.HasIndex("ApplicationId", "BoardMemberUserId")
+ .IsUnique();
+
+ b.ToTable("board_votes", (string)null);
+ });
+
+ modelBuilder.Entity("Humans.Domain.Entities.BudgetAuditLog", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("ActorUserId")
+ .HasColumnType("uuid");
+
+ b.Property("BudgetYearId")
+ .HasColumnType("uuid");
+
+ b.Property("Description")
+ .IsRequired()
+ .HasMaxLength(1000)
+ .HasColumnType("character varying(1000)");
+
+ b.Property("EntityId")
+ .HasColumnType("uuid");
+
+ b.Property("EntityType")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("character varying(100)");
+
+ b.Property("FieldName")
+ .HasMaxLength(100)
+ .HasColumnType("character varying(100)");
+
+ b.Property("NewValue")
+ .HasMaxLength(1000)
+ .HasColumnType("character varying(1000)");
+
+ b.Property("OccurredAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("OldValue")
+ .HasMaxLength(1000)
+ .HasColumnType("character varying(1000)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ActorUserId");
+
+ b.HasIndex("BudgetYearId");
+
+ b.HasIndex("OccurredAt");
+
+ b.HasIndex("EntityType", "EntityId");
+
+ b.ToTable("budget_audit_logs", (string)null);
+ });
+
+ modelBuilder.Entity("Humans.Domain.Entities.BudgetCategory", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("AllocatedAmount")
+ .HasPrecision(18, 2)
+ .HasColumnType("numeric(18,2)");
+
+ b.Property("BudgetGroupId")
+ .HasColumnType("uuid");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("ExpenditureType")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("character varying(50)");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.Property("SortOrder")
+ .HasColumnType("integer");
+
+ b.Property("TeamId")
+ .HasColumnType("uuid");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.HasKey("Id");
+
+ b.HasIndex("TeamId")
+ .HasFilter("\"TeamId\" IS NOT NULL");
+
+ b.HasIndex("BudgetGroupId", "SortOrder");
+
+ b.ToTable("budget_categories", (string)null);
+ });
+
+ modelBuilder.Entity("Humans.Domain.Entities.BudgetGroup", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("BudgetYearId")
+ .HasColumnType("uuid");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("IsDepartmentGroup")
+ .HasColumnType("boolean");
+
+ b.Property("IsRestricted")
+ .HasColumnType("boolean");
+
+ b.Property("IsTicketingGroup")
+ .HasColumnType("boolean");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.Property("SortOrder")
+ .HasColumnType("integer");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.HasKey("Id");
+
+ b.HasIndex("BudgetYearId", "SortOrder");
+
+ b.ToTable("budget_groups", (string)null);
+ });
+
+ modelBuilder.Entity("Humans.Domain.Entities.BudgetLineItem", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Amount")
+ .HasPrecision(18, 2)
+ .HasColumnType("numeric(18,2)");
+
+ b.Property("BudgetCategoryId")
+ .HasColumnType("uuid");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Description")
+ .IsRequired()
+ .HasMaxLength(500)
+ .HasColumnType("character varying(500)");
+
+ b.Property("ExpectedDate")
+ .HasColumnType("date");
+
+ b.Property("IsAutoGenerated")
+ .HasColumnType("boolean");
+
+ b.Property("IsCashflowOnly")
+ .HasColumnType("boolean");
+
+ b.Property("Notes")
+ .HasMaxLength(2000)
+ .HasColumnType("character varying(2000)");
+
+ b.Property("ResponsibleTeamId")
+ .HasColumnType("uuid");
+
+ b.Property("SortOrder")
+ .HasColumnType("integer");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("VatRate")
+ .HasColumnType("integer");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ResponsibleTeamId")
+ .HasFilter("\"ResponsibleTeamId\" IS NOT NULL");
+
+ b.HasIndex("BudgetCategoryId", "SortOrder");
+
+ b.ToTable("budget_line_items", (string)null);
+ });
+
+ modelBuilder.Entity("Humans.Domain.Entities.BudgetYear", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("DeletedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("IsDeleted")
+ .HasColumnType("boolean");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.Property("Status")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("character varying(50)");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Year")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("character varying(50)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Status");
+
+ b.HasIndex("Year")
+ .IsUnique();
+
+ b.ToTable("budget_years", (string)null);
+ });
+
+ modelBuilder.Entity("Humans.Domain.Entities.CalendarEvent", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("CreatedByUserId")
+ .HasColumnType("uuid");
+
+ b.Property("DeletedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Description")
+ .HasMaxLength(4000)
+ .HasColumnType("character varying(4000)");
+
+ b.Property("EndUtc")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("IsAllDay")
+ .HasColumnType("boolean");
+
+ b.Property("Location")
+ .HasMaxLength(500)
+ .HasColumnType("character varying(500)");
+
+ b.Property("LocationUrl")
+ .HasMaxLength(2000)
+ .HasColumnType("character varying(2000)");
+
+ b.Property("OwningTeamId")
+ .HasColumnType("uuid");
+
+ b.Property("RecurrenceRule")
+ .HasMaxLength(500)
+ .HasColumnType("character varying(500)");
+
+ b.Property("RecurrenceTimezone")
+ .HasMaxLength(100)
+ .HasColumnType("character varying(100)");
+
+ b.Property("RecurrenceUntilUtc")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("StartUtc")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Title")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("character varying(200)");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.HasKey("Id");
+
+ b.HasIndex("OwningTeamId", "StartUtc");
+
+ b.HasIndex("StartUtc", "RecurrenceUntilUtc");
+
+ b.ToTable("calendar_events", (string)null);
+ });
+
+ modelBuilder.Entity("Humans.Domain.Entities.CalendarEventException", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("CreatedByUserId")
+ .HasColumnType("uuid");
+
+ b.Property("EventId")
+ .HasColumnType("uuid");
+
+ b.Property("IsCancelled")
+ .HasColumnType("boolean");
+
+ b.Property("OriginalOccurrenceStartUtc")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("OverrideDescription")
+ .HasMaxLength(4000)
+ .HasColumnType("character varying(4000)");
+
+ b.Property("OverrideEndUtc")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("OverrideLocation")
+ .HasMaxLength(500)
+ .HasColumnType("character varying(500)");
+
+ b.Property("OverrideLocationUrl")
+ .HasMaxLength(2000)
+ .HasColumnType("character varying(2000)");
+
+ b.Property("OverrideStartUtc")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("OverrideTitle")
+ .HasMaxLength(200)
+ .HasColumnType("character varying(200)");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.HasKey("Id");
+
+ b.HasIndex("EventId", "OriginalOccurrenceStartUtc")
+ .IsUnique();
+
+ b.ToTable("calendar_event_exceptions", (string)null);
+ });
+
+ modelBuilder.Entity("Humans.Domain.Entities.Camp", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("ContactEmail")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.Property("ContactPhone")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("CreatedByUserId")
+ .HasColumnType("uuid");
+
+ b.Property("HideHistoricalNames")
+ .HasColumnType("boolean");
+
+ b.Property("IsSwissCamp")
+ .HasColumnType("boolean");
+
+ b.Property("Links")
+ .HasColumnType("jsonb");
+
+ b.Property("Slug")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.Property("TimesAtNowhere")
+ .HasColumnType("integer");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("WebOrSocialUrl")
+ .HasMaxLength(512)
+ .HasColumnType("character varying(512)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CreatedByUserId");
+
+ b.HasIndex("Slug")
+ .IsUnique();
+
+ b.ToTable("camps", (string)null);
+ });
+
+ modelBuilder.Entity("Humans.Domain.Entities.CampHistoricalName", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CampId")
+ .HasColumnType("uuid");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.Property("Source")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("character varying(50)");
+
+ b.Property("Year")
+ .HasColumnType("integer");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CampId");
+
+ b.ToTable("camp_historical_names", (string)null);
+ });
+
+ modelBuilder.Entity("Humans.Domain.Entities.CampImage", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CampId")
+ .HasColumnType("uuid");
+
+ b.Property("ContentType")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)");
+
+ b.Property("FileName")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.Property("SortOrder")
+ .HasColumnType("integer");
+
+ b.Property("StoragePath")
+ .IsRequired()
+ .HasMaxLength(512)
+ .HasColumnType("character varying(512)");
+
+ b.Property("UploadedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CampId");
+
+ b.ToTable("camp_images", (string)null);
+ });
+
+ modelBuilder.Entity("Humans.Domain.Entities.CampLead", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CampId")
+ .HasColumnType("uuid");
+
+ b.Property("JoinedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("LeftAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Role")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("character varying(50)");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.HasIndex("CampId", "UserId")
+ .IsUnique()
+ .HasDatabaseName("IX_camp_leads_active_unique")
+ .HasFilter("\"LeftAt\" IS NULL");
+
+ b.ToTable("camp_leads", (string)null);
+ });
+
+ modelBuilder.Entity("Humans.Domain.Entities.CampMember", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CampSeasonId")
+ .HasColumnType("uuid");
+
+ b.Property("ConfirmedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("ConfirmedByUserId")
+ .HasColumnType("uuid");
+
+ b.Property("HasEarlyEntry")
+ .HasColumnType("boolean");
+
+ b.Property("RemovedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("RemovedByUserId")
+ .HasColumnType("uuid");
+
+ b.Property("RequestedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Status")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("character varying(50)");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.HasIndex("CampSeasonId", "UserId")
+ .IsUnique()
+ .HasDatabaseName("IX_camp_members_active_unique")
+ .HasFilter("\"Status\" <> 'Removed'");
+
+ b.ToTable("camp_members", (string)null);
+ });
+
+ modelBuilder.Entity("Humans.Domain.Entities.CampPolygon", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("AreaSqm")
+ .HasColumnType("double precision");
+
+ b.Property("CampSeasonId")
+ .HasColumnType("uuid");
+
+ b.Property("GeoJson")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("LastModifiedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("LastModifiedByUserId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CampSeasonId")
+ .IsUnique();
+
+ b.HasIndex("LastModifiedByUserId");
+
+ b.ToTable("camp_polygons", (string)null);
+ });
+
+ modelBuilder.Entity("Humans.Domain.Entities.CampPolygonHistory", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("AreaSqm")
+ .HasColumnType("double precision");
+
+ b.Property("CampSeasonId")
+ .HasColumnType("uuid");
+
+ b.Property("GeoJson")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("ModifiedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("ModifiedByUserId")
+ .HasColumnType("uuid");
+
+ b.Property("Note")
+ .IsRequired()
+ .HasMaxLength(512)
+ .HasColumnType("character varying(512)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ModifiedByUserId");
+
+ b.HasIndex("CampSeasonId", "ModifiedAt");
+
+ b.ToTable("camp_polygon_histories", (string)null);
+ });
+
+ modelBuilder.Entity("Humans.Domain.Entities.CampRoleAssignment", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("AssignedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("AssignedByUserId")
+ .HasColumnType("uuid");
+
+ b.Property("CampMemberId")
+ .HasColumnType("uuid");
+
+ b.Property("CampRoleDefinitionId")
+ .HasColumnType("uuid");
+
+ b.Property("CampSeasonId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CampMemberId");
+
+ b.HasIndex("CampRoleDefinitionId");
+
+ b.HasIndex("CampSeasonId", "CampRoleDefinitionId", "CampMemberId")
+ .IsUnique()
+ .HasDatabaseName("IX_camp_role_assignments_unique");
+
+ b.ToTable("camp_role_assignments", (string)null);
+ });
+
+ modelBuilder.Entity("Humans.Domain.Entities.CampRoleDefinition", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("DeactivatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Description")
+ .HasMaxLength(2000)
+ .HasColumnType("character varying(2000)");
+
+ b.Property("MinimumRequired")
+ .HasColumnType("integer");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("character varying(100)");
+
+ b.Property("SlotCount")
+ .HasColumnType("integer");
+
+ b.Property("Slug")
+ .IsRequired()
+ .HasMaxLength(60)
+ .HasColumnType("character varying(60)");
+
+ b.Property("SortOrder")
+ .HasColumnType("integer");
+
+ b.Property("SpecialRole")
+ .IsRequired()
+ .ValueGeneratedOnAdd()
+ .HasMaxLength(50)
+ .HasColumnType("character varying(50)")
+ .HasDefaultValueSql("'None'");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Name")
+ .IsUnique()
+ .HasDatabaseName("IX_camp_role_definitions_name_unique");
+
+ b.HasIndex("SortOrder");
+
+ b.ToTable("camp_role_definitions", (string)null);
+ });
+
+ modelBuilder.Entity("Humans.Domain.Entities.CampSeason", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("AcceptingMembers")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("character varying(50)");
+
+ b.Property("AdultPlayspace")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("character varying(50)");
+
+ b.Property("BlurbLong")
+ .IsRequired()
+ .HasMaxLength(4000)
+ .HasColumnType("character varying(4000)");
+
+ b.Property("BlurbShort")
+ .IsRequired()
+ .HasMaxLength(1000)
+ .HasColumnType("character varying(1000)");
+
+ b.Property("CampId")
+ .HasColumnType("uuid");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("EeSlotCount")
+ .HasColumnType("integer");
+
+ b.Property("ElectricalGrid")
+ .HasMaxLength(50)
+ .HasColumnType("character varying(50)");
+
+ b.Property("HasPerformanceSpace")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("character varying(50)");
+
+ b.Property