diff --git a/ManagerService/Data/MyInfoMateDbContext.cs b/ManagerService/Data/MyInfoMateDbContext.cs index 9671081..df20721 100644 --- a/ManagerService/Data/MyInfoMateDbContext.cs +++ b/ManagerService/Data/MyInfoMateDbContext.cs @@ -529,47 +529,61 @@ namespace ManagerService.Data // profondeur (HasAdvancedStats), pas sur la durée — décidé le 2026-08-09. // Au-delà, les VisitEvent sont purgés (job `visit-events-purge`). - // Seed : plans d'abonnement par défaut + // Seed : plans d'abonnement, alignés sur la grille de tarifs de + // myinfomate-landing (source de vérité) — Essentiel 39€ / Pro 99€ / + // Premium 179€ / Enterprise sur devis. + // + // Ce que le plan NE porte pas : app native, offline + beacons, push et + // traduction automatique sont dans la grille mais pas dans cette table. + // Ils se règlent par instance (IsMobile, IsWeb…) : la base ne les applique + // pas, l'affectation le fait. + // + // ⚠️ Sémantique de 0, asymétrique et volontaire : pour StorageQuotaBytes, + // 0 = illimité ; pour AiTokensPerMonth, 0 = pas d'IA. D'où la sentinelle + // long.MaxValue sur Enterprise plutôt qu'un 0 qui le priverait d'assistant. modelBuilder.Entity().HasData( new SubscriptionPlan { - Id = "plan-starter", - Name = "Starter", - StorageQuotaBytes = 1L * 1024 * 1024 * 1024, // 1 GB + Id = "plan-essentiel", + Name = "Essentiel", + StorageQuotaBytes = 1L * 1024 * 1024 * 1024, // 1 GB + // L'assistant IA n'est pas inclus dans ce plan. Il se vend en add-on, + // activé en surchargeant Instance.AiTokensPerMonth sans changer de plan + // (ApplyPlanQuotas ne repart du plan qu'au changement de plan). AiTokensPerMonth = 0, - HasStats = false, - StatsHistoryDays = 30, + HasStats = true, + StatsHistoryDays = 30, // « Visiteurs / jour » HasAdvancedStats = false, }, new SubscriptionPlan { - Id = "plan-standard", - Name = "Standard", - StorageQuotaBytes = 10L * 1024 * 1024 * 1024, // 10 GB - AiTokensPerMonth = 5_000_000, // ~500 req/mois * ~10k tokens/req estimés, à ajuster selon l'usage réel observé + Id = "plan-pro", + Name = "Pro", + StorageQuotaBytes = 15L * 1024 * 1024 * 1024, // 15 GB + AiTokensPerMonth = 0, // non inclus, cf. Essentiel HasStats = true, - StatsHistoryDays = StatsRetentionDays, + StatsHistoryDays = 30, HasAdvancedStats = false, }, new SubscriptionPlan { Id = "plan-premium", Name = "Premium", - StorageQuotaBytes = 50L * 1024 * 1024 * 1024, // 50 GB - AiTokensPerMonth = 20_000_000, // ~2000 req/mois * ~10k tokens/req estimés, à ajuster selon l'usage réel observé + StorageQuotaBytes = 50L * 1024 * 1024 * 1024, // 50 GB + AiTokensPerMonth = 20_000_000, // ~2 000 req/mois * ~10k jetons HasStats = true, StatsHistoryDays = StatsRetentionDays, HasAdvancedStats = true, }, new SubscriptionPlan { - Id = "plan-essentiel", - Name = "Essentiel", - StorageQuotaBytes = 1L * 1024 * 1024 * 1024, // 1 GB - AiTokensPerMonth = 500_000, // ~50 req/mois * ~10k tokens/req estimés — IA reste surtout un add-on payant sur ce plan + Id = "plan-enterprise", + Name = "Enterprise", + StorageQuotaBytes = 0, // 0 = illimité + AiTokensPerMonth = long.MaxValue, // « quota sur mesure » HasStats = true, StatsHistoryDays = StatsRetentionDays, - HasAdvancedStats = false, + HasAdvancedStats = true, } ); } diff --git a/ManagerService/Migrations/20260811140135_AlignSubscriptionPlansWithPricing.Designer.cs b/ManagerService/Migrations/20260811140135_AlignSubscriptionPlansWithPricing.Designer.cs new file mode 100644 index 0000000..3ff809c --- /dev/null +++ b/ManagerService/Migrations/20260811140135_AlignSubscriptionPlansWithPricing.Designer.cs @@ -0,0 +1,1883 @@ +// +using System; +using System.Collections.Generic; +using Manager.DTOs; +using ManagerService.DTOs; +using ManagerService.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using NetTopologySuite.Geometries; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Pgvector; + +#nullable disable + +namespace ManagerService.Migrations +{ + [DbContext(typeof(MyInfoMateDbContext))] + [Migration("20260811140135_AlignSubscriptionPlansWithPricing")] + partial class AlignSubscriptionPlansWithPricing + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.2") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "postgis"); + NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "vector"); + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("ManagerService.Data.ApiKey", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AppType") + .HasColumnType("integer"); + + b.Property("DateCreation") + .HasColumnType("timestamp with time zone"); + + b.Property("DateExpiration") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdate") + .HasColumnType("timestamp with time zone"); + + b.Property("InstanceId") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("KeyHash") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.ToTable("ApiKeys"); + }); + + modelBuilder.Entity("ManagerService.Data.AppConfigurationLink", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ApplicationInstanceId") + .IsRequired() + .HasColumnType("text"); + + b.Property("ConfigurationId") + .IsRequired() + .HasColumnType("text"); + + b.Property("DateCreation") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdate") + .HasColumnType("timestamp with time zone"); + + b.Property("DeviceId") + .HasColumnType("text"); + + b.Property("GridColSpan") + .HasColumnType("integer"); + + b.Property("GridRowSpan") + .HasColumnType("integer"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("IsDate") + .HasColumnType("boolean"); + + b.Property("IsHour") + .HasColumnType("boolean"); + + b.Property("IsSectionImageBackground") + .HasColumnType("boolean"); + + b.Property("LoaderImageId") + .HasColumnType("text"); + + b.Property("LoaderImageUrl") + .HasColumnType("text"); + + b.Property("Order") + .HasColumnType("integer"); + + b.Property("PrimaryColor") + .HasColumnType("text"); + + b.Property("RoundedValue") + .HasColumnType("integer"); + + b.Property("ScreenPercentageSectionsMainPage") + .HasColumnType("integer"); + + b.Property("SecondaryColor") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationInstanceId"); + + b.HasIndex("ConfigurationId"); + + b.HasIndex("DeviceId"); + + b.ToTable("AppConfigurationLinks"); + }); + + modelBuilder.Entity("ManagerService.Data.ApplicationInstance", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AppType") + .HasColumnType("integer"); + + b.Property("DateCreation") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdate") + .HasColumnType("timestamp with time zone"); + + b.Property("InstanceId") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsAssistant") + .HasColumnType("boolean"); + + b.PrimitiveCollection>("Languages") + .HasColumnType("text[]"); + + b.Property("LoaderImageId") + .HasColumnType("text"); + + b.Property("LoaderImageUrl") + .HasColumnType("text"); + + b.Property("MainImageId") + .HasColumnType("text"); + + b.Property("MainImageUrl") + .HasColumnType("text"); + + b.Property("PrimaryColor") + .HasColumnType("text"); + + b.Property("SecondaryColor") + .HasColumnType("text"); + + b.Property("SectionEventId") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.HasIndex("SectionEventId"); + + b.ToTable("ApplicationInstances"); + }); + + modelBuilder.Entity("ManagerService.Data.AuditLog", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("Action") + .HasColumnType("text"); + + b.Property("EntityId") + .HasColumnType("text"); + + b.Property("EntityType") + .HasColumnType("text"); + + b.Property("InstanceId") + .HasColumnType("text"); + + b.Property("NewValues") + .HasColumnType("text"); + + b.Property("OldValues") + .HasColumnType("text"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.ToTable("AuditLogs"); + }); + + modelBuilder.Entity("ManagerService.Data.Configuration", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("DateCreation") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdate") + .HasColumnType("timestamp with time zone"); + + b.Property("ImageId") + .HasColumnType("text"); + + b.Property("ImageSource") + .HasColumnType("text"); + + b.Property("InstanceId") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsOffline") + .HasColumnType("boolean"); + + b.Property("IsQRCode") + .HasColumnType("boolean"); + + b.Property("IsSearchNumber") + .HasColumnType("boolean"); + + b.Property("IsSearchText") + .HasColumnType("boolean"); + + b.Property("Label") + .IsRequired() + .HasColumnType("text"); + + b.PrimitiveCollection>("Languages") + .HasColumnType("text[]"); + + b.Property("LoaderImageId") + .HasColumnType("text"); + + b.Property("LoaderImageUrl") + .HasColumnType("text"); + + b.Property("PrimaryColor") + .HasColumnType("text"); + + b.Property("SecondaryColor") + .HasColumnType("text"); + + b.Property("Title") + .IsRequired() + .HasColumnType("jsonb"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.ToTable("Configurations"); + }); + + modelBuilder.Entity("ManagerService.Data.ContentEmbedding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ChunkIndex") + .HasColumnType("integer"); + + b.Property("ConfigurationId") + .HasColumnType("text"); + + b.Property("ContentId") + .IsRequired() + .HasColumnType("text"); + + b.Property("ContentType") + .HasColumnType("integer"); + + b.Property("Embedding") + .IsRequired() + .HasColumnType("vector(768)"); + + b.Property("InstanceId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Language") + .IsRequired() + .HasMaxLength(5) + .HasColumnType("character varying(5)"); + + b.Property("PageNumber") + .HasColumnType("integer"); + + b.Property("Text") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Embedding"); + + NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("Embedding"), "hnsw"); + NpgsqlIndexBuilderExtensions.HasOperators(b.HasIndex("Embedding"), new[] { "vector_cosine_ops" }); + + b.HasIndex("InstanceId", "ContentType"); + + b.HasIndex("ContentType", "ContentId", "ChunkIndex") + .IsUnique(); + + b.ToTable("ContentEmbeddings"); + }); + + modelBuilder.Entity("ManagerService.Data.Device", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AppVersion") + .HasColumnType("text"); + + b.Property("BatteryLevel") + .HasColumnType("text"); + + b.Property("ConfigurationId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Connected") + .HasColumnType("boolean"); + + b.Property("ConnectionLevel") + .HasColumnType("text"); + + b.Property("DateCreation") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdate") + .HasColumnType("timestamp with time zone"); + + b.Property("Identifier") + .HasColumnType("text"); + + b.Property("InstanceId") + .IsRequired() + .HasColumnType("text"); + + b.Property("IpAddressETH") + .HasColumnType("text"); + + b.Property("IpAddressWLAN") + .HasColumnType("text"); + + b.Property("LastBatteryLevel") + .HasColumnType("timestamp with time zone"); + + b.Property("LastConnectionLevel") + .HasColumnType("timestamp with time zone"); + + b.Property("LastSeen") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ConfigurationId"); + + b.HasIndex("InstanceId"); + + b.ToTable("Devices"); + }); + + modelBuilder.Entity("ManagerService.Data.Instance", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AiTokensPerMonth") + .HasColumnType("bigint"); + + b.Property("AiTokensThisMonth") + .HasColumnType("bigint"); + + b.Property("AiUsageMonthKey") + .HasColumnType("text"); + + b.Property("BillingAddress") + .HasColumnType("text"); + + b.Property("BillingCountry") + .HasColumnType("text"); + + b.Property("DateCreation") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdate") + .HasColumnType("timestamp with time zone"); + + b.Property("GuideFallbackMessages") + .HasColumnType("jsonb"); + + b.Property("GuideName") + .HasColumnType("text"); + + b.Property("GuidePersonaPrompt") + .HasColumnType("text"); + + b.Property("GuideVoiceId") + .HasColumnType("text"); + + b.Property("HasAdvancedStats") + .HasColumnType("boolean"); + + b.Property("HasStats") + .HasColumnType("boolean"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("IsAssistant") + .HasColumnType("boolean"); + + b.Property("IsImageWatermark") + .HasColumnType("boolean"); + + b.Property("IsMobile") + .HasColumnType("boolean"); + + b.Property("IsPushNotification") + .HasColumnType("boolean"); + + b.Property("IsTablet") + .HasColumnType("boolean"); + + b.Property("IsTrialActive") + .HasColumnType("boolean"); + + b.Property("IsVR") + .HasColumnType("boolean"); + + b.Property("IsWeb") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("PinCode") + .HasColumnType("text"); + + b.Property("PublicApiKey") + .HasColumnType("text"); + + b.Property("StatsHistoryDays") + .HasColumnType("integer"); + + b.Property("StorageQuotaBytes") + .HasColumnType("bigint"); + + b.Property("StripeCustomerId") + .HasColumnType("text"); + + b.Property("StripeSubscriptionId") + .HasColumnType("text"); + + b.Property("SubscriptionPlanId") + .HasColumnType("text"); + + b.Property("TrialAiTokensUsed") + .HasColumnType("bigint"); + + b.Property("TrialCheckInEmailSent") + .HasColumnType("boolean"); + + b.Property("TrialEndsAt") + .HasColumnType("timestamp with time zone"); + + b.Property("TrialLastDayEmailSent") + .HasColumnType("boolean"); + + b.Property("TrialReminderEmailSent") + .HasColumnType("boolean"); + + b.Property("VatNumber") + .HasColumnType("text"); + + b.Property("VatRate") + .HasColumnType("numeric"); + + b.Property("WebSlug") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("SubscriptionPlanId"); + + b.ToTable("Instances"); + }); + + modelBuilder.Entity("ManagerService.Data.PushNotification", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("Body") + .IsRequired() + .HasColumnType("text"); + + b.Property("DateCreation") + .HasColumnType("timestamp with time zone"); + + b.Property("HangfireJobId") + .HasColumnType("text"); + + b.Property("InstanceId") + .IsRequired() + .HasColumnType("text"); + + b.Property("ScheduledAt") + .HasColumnType("timestamp with time zone"); + + b.Property("SentAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text"); + + b.Property("Topic") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.ToTable("PushNotifications"); + }); + + modelBuilder.Entity("ManagerService.Data.Resource", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AiChunkCount") + .HasColumnType("integer"); + + b.Property("AiIndexMessage") + .HasColumnType("text"); + + b.Property("AiIndexStatus") + .HasColumnType("integer"); + + b.Property("AiIndexedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DateCreation") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdate") + .HasColumnType("timestamp with time zone"); + + b.Property("FileName") + .HasColumnType("text"); + + b.Property("IncludeInAiKnowledge") + .HasColumnType("boolean"); + + b.Property("InstanceId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Label") + .IsRequired() + .HasColumnType("text"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StoragePath") + .HasColumnType("text"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("Url") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.ToTable("Resources"); + }); + + modelBuilder.Entity("ManagerService.Data.Section", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("BeaconId") + .HasColumnType("integer"); + + b.Property("ConfigurationId") + .IsRequired() + .HasColumnType("text"); + + b.Property("DateCreation") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdate") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .HasColumnType("jsonb"); + + b.Property("Discriminator") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ImageId") + .HasColumnType("text"); + + b.Property("ImageSource") + .HasColumnType("text"); + + b.Property("InstanceId") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("IsBeacon") + .HasColumnType("boolean"); + + b.Property("IsSubSection") + .HasColumnType("boolean"); + + b.Property("Label") + .IsRequired() + .HasColumnType("text"); + + b.Property("Latitude") + .HasColumnType("text"); + + b.Property("Longitude") + .HasColumnType("text"); + + b.Property("MeterZoneGPS") + .HasColumnType("integer"); + + b.Property("Order") + .HasColumnType("integer"); + + b.Property("ParentId") + .HasColumnType("text"); + + b.Property("SectionMenuId") + .HasColumnType("text"); + + b.Property("Title") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.HasIndex("SectionMenuId"); + + b.ToTable("Sections"); + + b.HasDiscriminator().HasValue("Base"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.EventAgenda", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .HasColumnType("jsonb"); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone"); + + b.Property("DateFrom") + .HasColumnType("timestamp with time zone"); + + b.Property("DateTo") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("IdVideoYoutube") + .HasColumnType("text"); + + b.Property("IsSynced") + .HasColumnType("boolean"); + + b.Property("Label") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Phone") + .HasColumnType("text"); + + b.Property("ResourceId") + .HasColumnType("text"); + + b.Property("SectionAgendaId") + .HasColumnType("text"); + + b.Property("SectionEventId") + .HasColumnType("text"); + + b.Property("SyncedImageUrl") + .HasColumnType("text"); + + b.Property("Type") + .HasColumnType("text"); + + b.Property("VideoLink") + .HasColumnType("text"); + + b.Property("VideoResourceId") + .HasColumnType("text"); + + b.Property("Website") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ResourceId"); + + b.HasIndex("SectionAgendaId"); + + b.HasIndex("SectionEventId"); + + b.HasIndex("VideoResourceId"); + + b.ToTable("EventAgendas"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.GeoPoint", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CategorieId") + .HasColumnType("integer"); + + b.Property("Contents") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Description") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Email") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Geometry") + .HasColumnType("geometry"); + + b.Property("ImageResourceId") + .HasColumnType("text"); + + b.Property("ImageUrl") + .HasColumnType("text"); + + b.Property("Phone") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("PolyColor") + .HasColumnType("text"); + + b.Property("Prices") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Schedules") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("SectionEventId") + .HasColumnType("text"); + + b.Property("SectionMapId") + .HasColumnType("text"); + + b.Property("Site") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Title") + .IsRequired() + .HasColumnType("jsonb"); + + b.HasKey("Id"); + + b.HasIndex("SectionEventId"); + + b.HasIndex("SectionMapId"); + + b.ToTable("GeoPoints"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.GuidedPath", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("DateCreation") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdate") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .HasColumnType("jsonb"); + + b.Property("EstimatedDurationMinutes") + .HasColumnType("integer"); + + b.Property("GameMessageDebut") + .HasColumnType("jsonb"); + + b.Property("GameMessageFin") + .HasColumnType("jsonb"); + + b.Property("HideNextStepsUntilComplete") + .HasColumnType("boolean"); + + b.Property("ImageResourceId") + .HasColumnType("text"); + + b.Property("InstanceId") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsGameMode") + .HasColumnType("boolean"); + + b.Property("IsLinear") + .HasColumnType("boolean"); + + b.Property("Order") + .HasColumnType("integer"); + + b.Property("RequireSuccessToAdvance") + .HasColumnType("boolean"); + + b.Property("SectionEventId") + .HasColumnType("text"); + + b.Property("SectionParcoursId") + .HasColumnType("text"); + + b.Property("Title") + .IsRequired() + .HasColumnType("jsonb"); + + b.HasKey("Id"); + + b.HasIndex("ImageResourceId"); + + b.HasIndex("InstanceId"); + + b.HasIndex("SectionEventId"); + + b.HasIndex("SectionParcoursId"); + + b.ToTable("GuidedPaths"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.GuidedStep", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AudioIds") + .HasColumnType("jsonb"); + + b.Property("Contents") + .HasColumnType("jsonb"); + + b.Property("Description") + .HasColumnType("jsonb"); + + b.Property("Geometry") + .HasColumnType("geometry"); + + b.Property("GuidedPathId") + .IsRequired() + .HasColumnType("text"); + + b.Property("ImageUrl") + .HasColumnType("text"); + + b.Property("IsGeoTriggered") + .HasColumnType("boolean"); + + b.Property("IsStepTimer") + .HasColumnType("boolean"); + + b.Property("Order") + .HasColumnType("integer"); + + b.Property("TimerExpiredMessage") + .HasColumnType("jsonb"); + + b.Property("TimerSeconds") + .HasColumnType("integer"); + + b.Property("Title") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("ZoneRadiusMeters") + .HasColumnType("double precision"); + + b.HasKey("Id"); + + b.HasIndex("GuidedPathId"); + + b.ToTable("GuidedSteps"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.QuizQuestion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("GuidedStepId") + .HasColumnType("text"); + + b.Property("IsSlidingPuzzle") + .HasColumnType("boolean"); + + b.Property>("Label") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Order") + .HasColumnType("integer"); + + b.Property("PuzzleCols") + .HasColumnType("integer"); + + b.Property("PuzzleImageId") + .HasColumnType("text"); + + b.Property("PuzzleRows") + .HasColumnType("integer"); + + b.Property("ResourceId") + .HasColumnType("text"); + + b.Property>("Responses") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("SectionQuizId") + .HasColumnType("text"); + + b.Property("ValidationQuestionType") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("GuidedStepId"); + + b.HasIndex("PuzzleImageId"); + + b.HasIndex("ResourceId"); + + b.HasIndex("SectionQuizId"); + + b.ToTable("QuizQuestions"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.SectionEvent+MapAnnotation", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("Geometry") + .HasColumnType("geometry"); + + b.Property("GeometryType") + .HasColumnType("integer"); + + b.Property("Icon") + .HasColumnType("text"); + + b.Property("IconResourceId") + .HasColumnType("text"); + + b.Property>("Label") + .HasColumnType("jsonb"); + + b.Property("PolyColor") + .HasColumnType("text"); + + b.Property("ProgrammeBlockId") + .HasColumnType("text"); + + b.Property("SectionEventId") + .HasColumnType("text"); + + b.Property>("Type") + .HasColumnType("jsonb"); + + b.HasKey("Id"); + + b.HasIndex("IconResourceId"); + + b.HasIndex("ProgrammeBlockId"); + + b.HasIndex("SectionEventId"); + + b.ToTable("MapAnnotations"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.SectionEvent+ProgrammeBlock", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property>("Description") + .HasColumnType("jsonb"); + + b.Property("EndTime") + .HasColumnType("timestamp with time zone"); + + b.Property("SectionEventId") + .HasColumnType("text"); + + b.Property("StartTime") + .HasColumnType("timestamp with time zone"); + + b.Property>("Title") + .HasColumnType("jsonb"); + + b.HasKey("Id"); + + b.HasIndex("SectionEventId"); + + b.ToTable("ProgrammeBlocks"); + }); + + modelBuilder.Entity("ManagerService.Data.SubscriptionPlan", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AiTokensPerMonth") + .HasColumnType("bigint"); + + b.Property("HasAdvancedStats") + .HasColumnType("boolean"); + + b.Property("HasStats") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("StatsHistoryDays") + .HasColumnType("integer"); + + b.Property("StorageQuotaBytes") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.ToTable("SubscriptionPlans"); + + b.HasData( + new + { + Id = "plan-essentiel", + AiTokensPerMonth = 0L, + HasAdvancedStats = false, + HasStats = true, + Name = "Essentiel", + StatsHistoryDays = 30, + StorageQuotaBytes = 1073741824L + }, + new + { + Id = "plan-pro", + AiTokensPerMonth = 0L, + HasAdvancedStats = false, + HasStats = true, + Name = "Pro", + StatsHistoryDays = 30, + StorageQuotaBytes = 16106127360L + }, + new + { + Id = "plan-premium", + AiTokensPerMonth = 20000000L, + HasAdvancedStats = true, + HasStats = true, + Name = "Premium", + StatsHistoryDays = 395, + StorageQuotaBytes = 53687091200L + }, + new + { + Id = "plan-enterprise", + AiTokensPerMonth = 9223372036854775807L, + HasAdvancedStats = true, + HasStats = true, + Name = "Enterprise", + StatsHistoryDays = 395, + StorageQuotaBytes = 0L + }); + }); + + modelBuilder.Entity("ManagerService.Data.User", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("DateCreation") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .IsRequired() + .HasColumnType("text"); + + b.Property("FirstName") + .HasColumnType("text"); + + b.Property("InstanceId") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastName") + .IsRequired() + .HasColumnType("text"); + + b.Property("Password") + .IsRequired() + .HasColumnType("text"); + + b.Property("PasswordTokenExpiresAt") + .HasColumnType("timestamp with time zone"); + + b.Property("PasswordTokenHash") + .HasColumnType("text"); + + b.Property("Role") + .HasColumnType("integer"); + + b.Property("Token") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("ManagerService.Data.VisitEvent", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AppType") + .HasColumnType("integer"); + + b.Property("ConfigurationId") + .HasColumnType("text"); + + b.Property("DurationSeconds") + .HasColumnType("integer"); + + b.Property("EventType") + .HasColumnType("integer"); + + b.Property("InstanceId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Language") + .HasColumnType("text"); + + b.Property("Metadata") + .HasColumnType("text"); + + b.Property("SectionId") + .HasColumnType("text"); + + b.Property("SessionId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.HasIndex("Timestamp"); + + b.ToTable("VisitEvents"); + }); + + modelBuilder.Entity("ManagerService.Data.VisitorQuestion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AppType") + .HasColumnType("integer"); + + b.Property("CitedContentIds") + .HasColumnType("jsonb"); + + b.Property("ConfigurationId") + .HasColumnType("text"); + + b.Property("ConversationId") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("HasAnswer") + .HasColumnType("boolean"); + + b.Property("InstanceId") + .HasColumnType("text"); + + b.Property("IsVoice") + .HasColumnType("boolean"); + + b.Property("Language") + .HasMaxLength(5) + .HasColumnType("character varying(5)"); + + b.Property("Question") + .HasColumnType("text"); + + b.Property("Reply") + .HasColumnType("text"); + + b.Property("ThemeId") + .HasColumnType("text"); + + b.Property("TokensUsed") + .HasColumnType("bigint"); + + b.Property("TopScore") + .HasColumnType("double precision"); + + b.HasKey("Id"); + + b.HasIndex("ConversationId"); + + b.HasIndex("InstanceId", "CreatedAt"); + + b.ToTable("VisitorQuestions"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.SectionAgenda", b => + { + b.HasBaseType("ManagerService.Data.Section"); + + b.Property("AgendaMapProvider") + .HasColumnType("integer"); + + b.Property>("AgendaResourceIds") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("IsOnlineAgenda") + .HasColumnType("boolean"); + + b.HasDiscriminator().HasValue("Agenda"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.SectionArticle", b => + { + b.HasBaseType("ManagerService.Data.Section"); + + b.Property>("ArticleAudioIds") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property>("ArticleContent") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property>("ArticleContents") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("ArticleIsContentTop") + .HasColumnType("boolean"); + + b.Property("ArticleIsReadAudioAuto") + .HasColumnType("boolean"); + + b.HasDiscriminator().HasValue("Article"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.SectionEvent", b => + { + b.HasBaseType("ManagerService.Data.Section"); + + b.Property("BaseSectionMapId") + .HasColumnType("text"); + + b.Property("EndDate") + .HasColumnType("timestamp with time zone"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone"); + + b.HasIndex("BaseSectionMapId"); + + b.HasDiscriminator().HasValue("Event"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.SectionGame", b => + { + b.HasBaseType("ManagerService.Data.Section"); + + b.Property>("GameMessageDebut") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property>("GameMessageFin") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("GamePuzzleCols") + .HasColumnType("integer"); + + b.Property("GamePuzzleImageId") + .HasColumnType("text"); + + b.Property("GamePuzzleRows") + .HasColumnType("integer"); + + b.Property("GameType") + .HasColumnType("integer"); + + b.HasIndex("GamePuzzleImageId"); + + b.HasDiscriminator().HasValue("Game"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.SectionMap", b => + { + b.HasBaseType("ManagerService.Data.Section"); + + b.Property("IconResourceId") + .HasColumnType("text"); + + b.Property("IsListViewEnabled") + .HasColumnType("boolean"); + + b.Property>("MapCategories") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("MapCenterLatitude") + .HasColumnType("text"); + + b.Property("MapCenterLongitude") + .HasColumnType("text"); + + b.Property("MapMapProvider") + .HasColumnType("integer"); + + b.Property("MapMapType") + .HasColumnType("integer"); + + b.Property("MapTypeMapbox") + .HasColumnType("integer"); + + b.Property("MapZoom") + .HasColumnType("integer"); + + b.HasIndex("IconResourceId"); + + b.HasDiscriminator().HasValue("Map"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.SectionMenu", b => + { + b.HasBaseType("ManagerService.Data.Section"); + + b.HasDiscriminator().HasValue("Menu"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.SectionParcours", b => + { + b.HasBaseType("ManagerService.Data.Section"); + + b.Property("BaseSectionMapId") + .HasColumnType("text"); + + b.Property("ShowMap") + .HasColumnType("boolean"); + + b.HasIndex("BaseSectionMapId"); + + b.ToTable("Sections", t => + { + t.Property("BaseSectionMapId") + .HasColumnName("SectionParcours_BaseSectionMapId"); + }); + + b.HasDiscriminator().HasValue("Parcours"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.SectionPdf", b => + { + b.HasBaseType("ManagerService.Data.Section"); + + b.Property>("PDFOrderedTranslationAndResources") + .IsRequired() + .HasColumnType("jsonb"); + + b.HasDiscriminator().HasValue("PDF"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.SectionQuiz", b => + { + b.HasBaseType("ManagerService.Data.Section"); + + b.Property>("QuizBadLevel") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property>("QuizGoodLevel") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property>("QuizGreatLevel") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property>("QuizMediumLevel") + .IsRequired() + .HasColumnType("jsonb"); + + b.HasDiscriminator().HasValue("Quiz"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.SectionSlider", b => + { + b.HasBaseType("ManagerService.Data.Section"); + + b.Property>("SliderContents") + .IsRequired() + .HasColumnType("jsonb"); + + b.HasDiscriminator().HasValue("Slider"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.SectionVideo", b => + { + b.HasBaseType("ManagerService.Data.Section"); + + b.Property("VideoSource") + .IsRequired() + .HasColumnType("text"); + + b.HasDiscriminator().HasValue("Video"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.SectionWeather", b => + { + b.HasBaseType("ManagerService.Data.Section"); + + b.Property("WeatherCity") + .HasColumnType("text"); + + b.Property("WeatherResult") + .HasColumnType("text"); + + b.Property("WeatherUpdatedDate") + .HasColumnType("timestamp with time zone"); + + b.HasDiscriminator().HasValue("Weather"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.SectionWeb", b => + { + b.HasBaseType("ManagerService.Data.Section"); + + b.Property("WebSource") + .IsRequired() + .HasColumnType("text"); + + b.HasDiscriminator().HasValue("Web"); + }); + + modelBuilder.Entity("ManagerService.Data.ApiKey", b => + { + b.HasOne("ManagerService.Data.Instance", "Instance") + .WithMany() + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("ManagerService.Data.AppConfigurationLink", b => + { + b.HasOne("ManagerService.Data.ApplicationInstance", "ApplicationInstance") + .WithMany("Configurations") + .HasForeignKey("ApplicationInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ManagerService.Data.Configuration", "Configuration") + .WithMany() + .HasForeignKey("ConfigurationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ManagerService.Data.Device", "Device") + .WithMany() + .HasForeignKey("DeviceId"); + + b.Navigation("ApplicationInstance"); + + b.Navigation("Configuration"); + + b.Navigation("Device"); + }); + + modelBuilder.Entity("ManagerService.Data.ApplicationInstance", b => + { + b.HasOne("ManagerService.Data.SubSection.SectionEvent", "SectionEvent") + .WithMany() + .HasForeignKey("SectionEventId"); + + b.Navigation("SectionEvent"); + }); + + modelBuilder.Entity("ManagerService.Data.Device", b => + { + b.HasOne("ManagerService.Data.Configuration", "Configuration") + .WithMany() + .HasForeignKey("ConfigurationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Configuration"); + }); + + modelBuilder.Entity("ManagerService.Data.Instance", b => + { + b.HasOne("ManagerService.Data.SubscriptionPlan", "SubscriptionPlan") + .WithMany() + .HasForeignKey("SubscriptionPlanId"); + + b.Navigation("SubscriptionPlan"); + }); + + modelBuilder.Entity("ManagerService.Data.Section", b => + { + b.HasOne("ManagerService.Data.SubSection.SectionMenu", null) + .WithMany("MenuSections") + .HasForeignKey("SectionMenuId"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.EventAgenda", b => + { + b.HasOne("ManagerService.Data.Resource", "Resource") + .WithMany() + .HasForeignKey("ResourceId"); + + b.HasOne("ManagerService.Data.SubSection.SectionAgenda", "SectionAgenda") + .WithMany("EventAgendas") + .HasForeignKey("SectionAgendaId"); + + b.HasOne("ManagerService.Data.SubSection.SectionEvent", "SectionEvent") + .WithMany() + .HasForeignKey("SectionEventId"); + + b.HasOne("ManagerService.Data.Resource", "VideoResource") + .WithMany() + .HasForeignKey("VideoResourceId"); + + b.Navigation("Resource"); + + b.Navigation("SectionAgenda"); + + b.Navigation("SectionEvent"); + + b.Navigation("VideoResource"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.GeoPoint", b => + { + b.HasOne("ManagerService.Data.SubSection.SectionEvent", "SectionEvent") + .WithMany() + .HasForeignKey("SectionEventId"); + + b.HasOne("ManagerService.Data.SubSection.SectionMap", "SectionMap") + .WithMany("MapPoints") + .HasForeignKey("SectionMapId"); + + b.Navigation("SectionEvent"); + + b.Navigation("SectionMap"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.GuidedPath", b => + { + b.HasOne("ManagerService.Data.Resource", "ImageResource") + .WithMany() + .HasForeignKey("ImageResourceId"); + + b.HasOne("ManagerService.Data.SubSection.SectionEvent", "SectionEvent") + .WithMany() + .HasForeignKey("SectionEventId"); + + b.HasOne("ManagerService.Data.SubSection.SectionParcours", "SectionParcours") + .WithMany("GuidedPaths") + .HasForeignKey("SectionParcoursId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("ImageResource"); + + b.Navigation("SectionEvent"); + + b.Navigation("SectionParcours"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.GuidedStep", b => + { + b.HasOne("ManagerService.Data.SubSection.GuidedPath", "GuidedPath") + .WithMany("Steps") + .HasForeignKey("GuidedPathId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("GuidedPath"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.QuizQuestion", b => + { + b.HasOne("ManagerService.Data.SubSection.GuidedStep", "GuidedStep") + .WithMany("QuizQuestions") + .HasForeignKey("GuidedStepId"); + + b.HasOne("ManagerService.Data.Resource", "PuzzleImage") + .WithMany() + .HasForeignKey("PuzzleImageId"); + + b.HasOne("ManagerService.Data.Resource", "Resource") + .WithMany() + .HasForeignKey("ResourceId"); + + b.HasOne("ManagerService.Data.SubSection.SectionQuiz", "SectionQuiz") + .WithMany("QuizQuestions") + .HasForeignKey("SectionQuizId"); + + b.Navigation("GuidedStep"); + + b.Navigation("PuzzleImage"); + + b.Navigation("Resource"); + + b.Navigation("SectionQuiz"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.SectionEvent+MapAnnotation", b => + { + b.HasOne("ManagerService.Data.Resource", "IconResource") + .WithMany() + .HasForeignKey("IconResourceId"); + + b.HasOne("ManagerService.Data.SubSection.SectionEvent+ProgrammeBlock", null) + .WithMany("MapAnnotations") + .HasForeignKey("ProgrammeBlockId"); + + b.HasOne("ManagerService.Data.SubSection.SectionEvent", null) + .WithMany("GlobalMapAnnotations") + .HasForeignKey("SectionEventId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("IconResource"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.SectionEvent+ProgrammeBlock", b => + { + b.HasOne("ManagerService.Data.SubSection.SectionEvent", null) + .WithMany("Programme") + .HasForeignKey("SectionEventId"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.SectionEvent", b => + { + b.HasOne("ManagerService.Data.SubSection.SectionMap", "BaseMap") + .WithMany() + .HasForeignKey("BaseSectionMapId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("BaseMap"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.SectionGame", b => + { + b.HasOne("ManagerService.Data.Resource", "GamePuzzleImage") + .WithMany() + .HasForeignKey("GamePuzzleImageId"); + + b.Navigation("GamePuzzleImage"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.SectionMap", b => + { + b.HasOne("ManagerService.Data.Resource", "IconResource") + .WithMany() + .HasForeignKey("IconResourceId"); + + b.Navigation("IconResource"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.SectionParcours", b => + { + b.HasOne("ManagerService.Data.SubSection.SectionMap", "BaseMap") + .WithMany() + .HasForeignKey("BaseSectionMapId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("BaseMap"); + }); + + modelBuilder.Entity("ManagerService.Data.ApplicationInstance", b => + { + b.Navigation("Configurations"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.GuidedPath", b => + { + b.Navigation("Steps"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.GuidedStep", b => + { + b.Navigation("QuizQuestions"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.SectionEvent+ProgrammeBlock", b => + { + b.Navigation("MapAnnotations"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.SectionAgenda", b => + { + b.Navigation("EventAgendas"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.SectionEvent", b => + { + b.Navigation("GlobalMapAnnotations"); + + b.Navigation("Programme"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.SectionMap", b => + { + b.Navigation("MapPoints"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.SectionMenu", b => + { + b.Navigation("MenuSections"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.SectionParcours", b => + { + b.Navigation("GuidedPaths"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.SectionQuiz", b => + { + b.Navigation("QuizQuestions"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/ManagerService/Migrations/20260811140135_AlignSubscriptionPlansWithPricing.cs b/ManagerService/Migrations/20260811140135_AlignSubscriptionPlansWithPricing.cs new file mode 100644 index 0000000..7d8c250 --- /dev/null +++ b/ManagerService/Migrations/20260811140135_AlignSubscriptionPlansWithPricing.cs @@ -0,0 +1,92 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional + +namespace ManagerService.Migrations +{ + /// + public partial class AlignSubscriptionPlansWithPricing : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + // Ordre imposé : les nouveaux plans d'abord, le repointage ensuite, les + // suppressions en dernier. EF avait scaffoldé les DeleteData en tête, ce qui + // aurait buté sur la clé étrangère Instances.SubscriptionPlanId — ou laissé + // des instances sans plan, donc à quotas nuls, donc sans IA ni stockage. + migrationBuilder.InsertData( + table: "SubscriptionPlans", + columns: new[] { "Id", "AiTokensPerMonth", "HasAdvancedStats", "HasStats", "Name", "StatsHistoryDays", "StorageQuotaBytes" }, + values: new object[,] + { + { "plan-enterprise", 9223372036854775807L, true, true, "Enterprise", 395, 0L }, + { "plan-pro", 0L, false, true, "Pro", 30, 16106127360L } + }); + + migrationBuilder.UpdateData( + table: "SubscriptionPlans", + keyColumn: "Id", + keyValue: "plan-essentiel", + columns: new[] { "AiTokensPerMonth", "StatsHistoryDays" }, + values: new object[] { 0L, 30 }); + + // « Standard » devient « Pro » : même offre, nom aligné sur la grille. + migrationBuilder.Sql( + @"UPDATE ""Instances"" SET ""SubscriptionPlanId"" = 'plan-pro' WHERE ""SubscriptionPlanId"" = 'plan-standard';"); + + // « Starter » n'a aucun équivalent commercial et donnait 0 jeton : les + // instances qui y pointaient sont détachées plutôt que rangées dans une + // offre qu'elles n'ont pas achetée. Un plan nul est visible à l'écran, + // un mauvais plan ne l'est pas. + migrationBuilder.Sql( + @"UPDATE ""Instances"" SET ""SubscriptionPlanId"" = NULL WHERE ""SubscriptionPlanId"" = 'plan-starter';"); + + migrationBuilder.DeleteData( + table: "SubscriptionPlans", + keyColumn: "Id", + keyValue: "plan-standard"); + + migrationBuilder.DeleteData( + table: "SubscriptionPlans", + keyColumn: "Id", + keyValue: "plan-starter"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DeleteData( + table: "SubscriptionPlans", + keyColumn: "Id", + keyValue: "plan-enterprise"); + + migrationBuilder.DeleteData( + table: "SubscriptionPlans", + keyColumn: "Id", + keyValue: "plan-pro"); + + migrationBuilder.UpdateData( + table: "SubscriptionPlans", + keyColumn: "Id", + keyValue: "plan-essentiel", + columns: new[] { "AiTokensPerMonth", "StatsHistoryDays" }, + values: new object[] { 500000L, 395 }); + + migrationBuilder.InsertData( + table: "SubscriptionPlans", + columns: new[] { "Id", "AiTokensPerMonth", "HasAdvancedStats", "HasStats", "Name", "StatsHistoryDays", "StorageQuotaBytes" }, + values: new object[,] + { + { "plan-standard", 5000000L, false, true, "Standard", 395, 10737418240L }, + { "plan-starter", 0L, false, false, "Starter", 30, 1073741824L } + }); + + // Repointage inverse. Les instances détachées de plan-starter ne sont pas + // rattachées : rien ne dit lesquelles l'étaient. + migrationBuilder.Sql( + @"UPDATE ""Instances"" SET ""SubscriptionPlanId"" = 'plan-standard' WHERE ""SubscriptionPlanId"" = 'plan-pro';"); + } + } +} diff --git a/ManagerService/Migrations/MyInfoMateDbContextModelSnapshot.cs b/ManagerService/Migrations/MyInfoMateDbContextModelSnapshot.cs index f0b91c6..ed6164c 100644 --- a/ManagerService/Migrations/MyInfoMateDbContextModelSnapshot.cs +++ b/ManagerService/Migrations/MyInfoMateDbContextModelSnapshot.cs @@ -1153,23 +1153,23 @@ namespace ManagerService.Migrations b.HasData( new { - Id = "plan-starter", + Id = "plan-essentiel", AiTokensPerMonth = 0L, HasAdvancedStats = false, - HasStats = false, - Name = "Starter", + HasStats = true, + Name = "Essentiel", StatsHistoryDays = 30, StorageQuotaBytes = 1073741824L }, new { - Id = "plan-standard", - AiTokensPerMonth = 5000000L, + Id = "plan-pro", + AiTokensPerMonth = 0L, HasAdvancedStats = false, HasStats = true, - Name = "Standard", - StatsHistoryDays = 395, - StorageQuotaBytes = 10737418240L + Name = "Pro", + StatsHistoryDays = 30, + StorageQuotaBytes = 16106127360L }, new { @@ -1183,13 +1183,13 @@ namespace ManagerService.Migrations }, new { - Id = "plan-essentiel", - AiTokensPerMonth = 500000L, - HasAdvancedStats = false, + Id = "plan-enterprise", + AiTokensPerMonth = 9223372036854775807L, + HasAdvancedStats = true, HasStats = true, - Name = "Essentiel", + Name = "Enterprise", StatsHistoryDays = 395, - StorageQuotaBytes = 1073741824L + StorageQuotaBytes = 0L }); });