diff --git a/Tiku.Api/Contracts/LearningDtos.cs b/Tiku.Api/Contracts/LearningDtos.cs
index 3d7b1f5..e0bc233 100644
--- a/Tiku.Api/Contracts/LearningDtos.cs
+++ b/Tiku.Api/Contracts/LearningDtos.cs
@@ -46,11 +46,6 @@ public sealed class PracticeSessionQueryDto
///
public Guid? PracticeSessionId { get; set; }
- ///
- /// 练习蓝图 ID。
- ///
- public Guid? BlueprintId { get; set; }
-
///
/// 模式。
///
@@ -73,7 +68,6 @@ public sealed class PracticeSessionQueryDto
{
return new PracticeSessionFilter(
routePracticeSessionId ?? PracticeSessionId,
- BlueprintId,
Mode,
Status,
Limit);
diff --git a/Tiku.Application/Learning/LearningActivityModels.cs b/Tiku.Application/Learning/LearningActivityModels.cs
index 29aafa6..ae70982 100644
--- a/Tiku.Application/Learning/LearningActivityModels.cs
+++ b/Tiku.Application/Learning/LearningActivityModels.cs
@@ -105,7 +105,6 @@ public sealed record PracticeSessionCommand(
public sealed record PracticeSessionFilter(
Guid? PracticeSessionId = null,
- Guid? BlueprintId = null,
string? Mode = null,
string? Status = null,
int? Limit = null);
@@ -167,12 +166,8 @@ public sealed record LearningActionResult(bool Ok, bool? IsFavorite = null);
public sealed record PracticeSessionItem(
Guid Id,
string Mode,
- string? TargetType,
- Guid? TargetId,
- Guid? BlueprintId,
- Guid? CollectionId,
- Guid? EntryId,
- Guid? ContentNodeId,
+ AccessResourceType ResourceType,
+ Guid ResourceId,
int QuestionCount,
int? DurationMinutes,
decimal? TotalScore,
@@ -215,8 +210,8 @@ public sealed record PracticeSessionQuestionItem(
public sealed record PracticeSessionReportItem(
Guid Id,
Guid PracticeSessionId,
- Guid? BlueprintId,
- Guid? CollectionId,
+ AccessResourceType ResourceType,
+ Guid ResourceId,
string Mode,
int TotalQuestions,
int AnsweredCount,
@@ -242,12 +237,8 @@ public sealed record PracticeSessionReportItem(
public sealed record PracticeHistoryItem(
Guid Id,
string Mode,
- string? TargetType,
- Guid? TargetId,
- Guid? BlueprintId,
- Guid? CollectionId,
- Guid? EntryId,
- Guid? ContentNodeId,
+ AccessResourceType ResourceType,
+ Guid ResourceId,
int QuestionCount,
int AnsweredCount,
int CorrectCount,
diff --git a/Tiku.Domain/Learning/LearningEntities.cs b/Tiku.Domain/Learning/LearningEntities.cs
index cc36666..667071f 100644
--- a/Tiku.Domain/Learning/LearningEntities.cs
+++ b/Tiku.Domain/Learning/LearningEntities.cs
@@ -6,13 +6,9 @@ namespace Tiku.Domain.Learning;
public sealed class PracticeSession : TenantEntity
{
public Guid UserId { get; set; }
- public Guid? BlueprintId { get; set; }
- public Guid? CollectionId { get; set; }
- public Guid? EntryId { get; set; }
- public Guid? ContentNodeId { get; set; }
+ public AccessResourceType ResourceType { get; set; }
+ public Guid ResourceId { get; set; }
public string Mode { get; set; } = "chapter";
- public string? TargetType { get; set; }
- public Guid? TargetId { get; set; }
public DateTimeOffset StartedAt { get; set; } = DateTimeOffset.UtcNow;
public DateTimeOffset? FinishedAt { get; set; }
public int QuestionCount { get; set; }
diff --git a/Tiku.Domain/Learning/LearningReportEntities.cs b/Tiku.Domain/Learning/LearningReportEntities.cs
index 1d48f91..580355e 100644
--- a/Tiku.Domain/Learning/LearningReportEntities.cs
+++ b/Tiku.Domain/Learning/LearningReportEntities.cs
@@ -95,8 +95,8 @@ public sealed class PracticeSessionReport : AuditableTenantEntity
{
public Guid UserId { get; set; }
public Guid PracticeSessionId { get; set; }
- public Guid? BlueprintId { get; set; }
- public Guid? CollectionId { get; set; }
+ public AccessResourceType ResourceType { get; set; }
+ public Guid ResourceId { get; set; }
public string Mode { get; set; } = "mock_exam";
public int TotalQuestions { get; set; }
public int AnsweredCount { get; set; }
diff --git a/Tiku.Infrastructure/Learning/Foundation/LearningFoundation.Helpers.cs b/Tiku.Infrastructure/Learning/Foundation/LearningFoundation.Helpers.cs
index 13f44cb..06e81c2 100644
--- a/Tiku.Infrastructure/Learning/Foundation/LearningFoundation.Helpers.cs
+++ b/Tiku.Infrastructure/Learning/Foundation/LearningFoundation.Helpers.cs
@@ -237,8 +237,8 @@ internal abstract partial class LearningActivityServiceBase
TenantId = actor.TenantId,
UserId = actor.UserId,
PracticeSessionId = session.Id,
- BlueprintId = session.BlueprintId,
- CollectionId = session.CollectionId,
+ ResourceType = session.ResourceType,
+ ResourceId = session.ResourceId,
Mode = session.Mode,
TotalQuestions = totalQuestions,
AnsweredCount = answeredCount,
@@ -358,12 +358,8 @@ internal abstract partial class LearningActivityServiceBase
return new PracticeSessionItem(
item.Id,
item.Mode,
- item.TargetType,
- item.TargetId,
- item.BlueprintId,
- item.CollectionId,
- item.EntryId,
- item.ContentNodeId,
+ item.ResourceType,
+ item.ResourceId,
item.QuestionCount,
item.DurationMinutes,
item.TotalScore,
@@ -389,8 +385,8 @@ internal abstract partial class LearningActivityServiceBase
return new PracticeSessionReportItem(
item.Id,
item.PracticeSessionId,
- item.BlueprintId,
- item.CollectionId,
+ item.ResourceType,
+ item.ResourceId,
item.Mode,
item.TotalQuestions,
item.AnsweredCount,
diff --git a/Tiku.Infrastructure/Learning/PracticeReports/PracticeReportService.cs b/Tiku.Infrastructure/Learning/PracticeReports/PracticeReportService.cs
index f8225f8..9c6fe9a 100644
--- a/Tiku.Infrastructure/Learning/PracticeReports/PracticeReportService.cs
+++ b/Tiku.Infrastructure/Learning/PracticeReports/PracticeReportService.cs
@@ -46,8 +46,6 @@ internal sealed class PracticeReportService(LearningServiceDependencies dependen
report.TenantId == actor.TenantId &&
report.UserId == actor.UserId);
- if (filter.BlueprintId.HasValue) query = query.Where(report => report.BlueprintId == filter.BlueprintId.Value);
-
if (!string.IsNullOrWhiteSpace(filter.Mode)) query = query.Where(report => report.Mode == filter.Mode.Trim());
var items = await query
@@ -85,12 +83,8 @@ internal sealed class PracticeReportService(LearningServiceDependencies dependen
.Select(row => new PracticeHistoryItem(
row.session.Id,
row.session.Mode,
- row.session.TargetType,
- row.session.TargetId,
- row.session.BlueprintId,
- row.session.CollectionId,
- row.session.EntryId,
- row.session.ContentNodeId,
+ row.session.ResourceType,
+ row.session.ResourceId,
row.session.QuestionCount,
row.report?.AnsweredCount ?? 0,
row.report?.CorrectCount ?? 0,
diff --git a/Tiku.Infrastructure/Learning/PracticeSessions/V2PracticeSessionService.cs b/Tiku.Infrastructure/Learning/PracticeSessions/V2PracticeSessionService.cs
index 0e4ccc8..6cd3c45 100644
--- a/Tiku.Infrastructure/Learning/PracticeSessions/V2PracticeSessionService.cs
+++ b/Tiku.Infrastructure/Learning/PracticeSessions/V2PracticeSessionService.cs
@@ -137,11 +137,8 @@ internal sealed class V2PracticeSessionService(LearningServiceDependencies depen
TenantId = actor.TenantId,
UserId = actor.UserId,
Mode = mode,
- TargetType = resourceType.ToString(),
- TargetId = resourceId,
- BlueprintId = resourceType == AccessResourceType.BlueprintRelease ? resourceId : null,
- CollectionId = resourceType == AccessResourceType.CollectionRelease ? resourceId : null,
- ContentNodeId = resourceType == AccessResourceType.CurriculumNode ? resourceId : null,
+ ResourceType = resourceType,
+ ResourceId = resourceId,
QuestionCount = distinctCandidates.Length,
DurationMinutes = durationMinutes,
TotalScore = totalScore,
diff --git a/Tiku.Infrastructure/Persistence/Configurations/LearningConfigurations.cs b/Tiku.Infrastructure/Persistence/Configurations/LearningConfigurations.cs
index e819a27..8bca2de 100644
--- a/Tiku.Infrastructure/Persistence/Configurations/LearningConfigurations.cs
+++ b/Tiku.Infrastructure/Persistence/Configurations/LearningConfigurations.cs
@@ -14,7 +14,7 @@ internal sealed class PracticeSessionConfiguration : IEntityTypeConfiguration new { entity.TenantId, entity.UserId, entity.Id });
builder.Property(entity => entity.Mode).HasMaxLength(50);
- builder.Property(entity => entity.TargetType).HasMaxLength(50);
+ builder.Property(entity => entity.ResourceType).HasSnakeCaseEnum();
builder.Property(entity => entity.StartedAt).HasDefaultValueSql("now()");
builder.Property(entity => entity.TotalScore).HasPrecision(8, 2);
builder.Property(entity => entity.Status).HasSnakeCaseEnum().HasDefaultValue(PracticeSessionStatus.Active);
@@ -38,22 +38,6 @@ internal sealed class PracticeSessionConfiguration : IEntityTypeConfiguration().WithMany()
.HasForeignKey(entity => entity.UserId)
.OnDelete(DeleteBehavior.Cascade);
- builder.HasOne().WithMany()
- .HasForeignKey(entity => new { entity.TenantId, entity.BlueprintId })
- .HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
- .OnDelete(DeleteBehavior.Restrict);
- builder.HasOne().WithMany()
- .HasForeignKey(entity => new { entity.TenantId, entity.CollectionId })
- .HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
- .OnDelete(DeleteBehavior.Restrict);
- builder.HasOne().WithMany()
- .HasForeignKey(entity => new { entity.TenantId, entity.EntryId })
- .HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
- .OnDelete(DeleteBehavior.Restrict);
- builder.HasOne().WithMany()
- .HasForeignKey(entity => new { entity.TenantId, entity.ContentNodeId })
- .HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
- .OnDelete(DeleteBehavior.Restrict);
}
}
diff --git a/Tiku.Infrastructure/Persistence/Configurations/LearningReportConfigurations.cs b/Tiku.Infrastructure/Persistence/Configurations/LearningReportConfigurations.cs
index 9016dc9..84709b7 100644
--- a/Tiku.Infrastructure/Persistence/Configurations/LearningReportConfigurations.cs
+++ b/Tiku.Infrastructure/Persistence/Configurations/LearningReportConfigurations.cs
@@ -185,6 +185,7 @@ internal sealed class PracticeSessionReportConfiguration : IEntityTypeConfigurat
builder.ConfigureTenantEntity("practice_session_reports");
builder.ConfigureTimestamps();
builder.Property(entity => entity.Mode).HasMaxLength(50);
+ builder.Property(entity => entity.ResourceType).HasSnakeCaseEnum();
builder.Property(entity => entity.Score).HasPrecision(10, 2);
builder.Property(entity => entity.TotalScore).HasPrecision(10, 2);
builder.Property(entity => entity.Accuracy).HasPrecision(6, 4);
@@ -199,8 +200,7 @@ internal sealed class PracticeSessionReportConfiguration : IEntityTypeConfigurat
builder.Property(entity => entity.Metadata).IsJson("{}");
builder.HasIndex(entity => new { entity.TenantId, entity.PracticeSessionId }).IsUnique();
builder.HasIndex(entity => new { entity.TenantId, entity.UserId, entity.SubmittedAt });
- builder.HasIndex(entity => new { entity.TenantId, entity.BlueprintId, entity.SubmittedAt })
- .HasFilter("blueprint_id is not null");
+ builder.HasIndex(entity => new { entity.TenantId, entity.ResourceType, entity.ResourceId, entity.SubmittedAt });
builder.ToTable(table =>
{
table.HasCheckConstraint("ck_practice_session_reports_counts",
@@ -217,14 +217,6 @@ internal sealed class PracticeSessionReportConfiguration : IEntityTypeConfigurat
.HasForeignKey(entity => new { entity.TenantId, entity.PracticeSessionId })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.Cascade);
- builder.HasOne().WithMany()
- .HasForeignKey(entity => new { entity.TenantId, entity.BlueprintId })
- .HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
- .OnDelete(DeleteBehavior.Restrict);
- builder.HasOne().WithMany()
- .HasForeignKey(entity => new { entity.TenantId, entity.CollectionId })
- .HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
- .OnDelete(DeleteBehavior.Restrict);
}
}
@@ -299,4 +291,4 @@ internal sealed class RevenueDailyStatConfiguration : IEntityTypeConfiguration new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.Restrict);
}
-}
\ No newline at end of file
+}
diff --git a/Tiku.Infrastructure/Persistence/Migrations/20260806021417_MigratePracticeSessionResourcesV2.Designer.cs b/Tiku.Infrastructure/Persistence/Migrations/20260806021417_MigratePracticeSessionResourcesV2.Designer.cs
new file mode 100644
index 0000000..4361bd7
--- /dev/null
+++ b/Tiku.Infrastructure/Persistence/Migrations/20260806021417_MigratePracticeSessionResourcesV2.Designer.cs
@@ -0,0 +1,26466 @@
+//
+using System;
+using System.Text.Json;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+using Tiku.Infrastructure.Persistence;
+
+#nullable disable
+
+namespace Tiku.Infrastructure.Persistence.Migrations
+{
+ [DbContext(typeof(TikuDbContext))]
+ [Migration("20260806021417_MigratePracticeSessionResourcesV2")]
+ partial class MigratePracticeSessionResourcesV2
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "10.0.10")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "citext");
+ NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "ltree");
+ NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "pg_trgm");
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("FriendlyName")
+ .HasColumnType("text")
+ .HasColumnName("friendly_name");
+
+ b.Property("Xml")
+ .HasColumnType("text")
+ .HasColumnName("xml");
+
+ b.HasKey("Id")
+ .HasName("pk_data_protection_keys");
+
+ b.ToTable("data_protection_keys", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("ClaimType")
+ .HasColumnType("text")
+ .HasColumnName("claim_type");
+
+ b.Property("ClaimValue")
+ .HasColumnType("text")
+ .HasColumnName("claim_value");
+
+ b.Property("UserId")
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.HasKey("Id")
+ .HasName("pk_user_claims");
+
+ b.HasIndex("UserId")
+ .HasDatabaseName("ix_user_claims_user_id");
+
+ b.ToTable("user_claims", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
+ {
+ b.Property("LoginProvider")
+ .HasColumnType("text")
+ .HasColumnName("login_provider");
+
+ b.Property("ProviderKey")
+ .HasColumnType("text")
+ .HasColumnName("provider_key");
+
+ b.Property("ProviderDisplayName")
+ .HasColumnType("text")
+ .HasColumnName("provider_display_name");
+
+ b.Property("UserId")
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.HasKey("LoginProvider", "ProviderKey")
+ .HasName("pk_user_logins");
+
+ b.HasIndex("UserId")
+ .HasDatabaseName("ix_user_logins_user_id");
+
+ b.ToTable("user_logins", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.Property("LoginProvider")
+ .HasColumnType("text")
+ .HasColumnName("login_provider");
+
+ b.Property("Name")
+ .HasColumnType("text")
+ .HasColumnName("name");
+
+ b.Property("Value")
+ .HasColumnType("text")
+ .HasColumnName("value");
+
+ b.HasKey("UserId", "LoginProvider", "Name")
+ .HasName("pk_user_tokens");
+
+ b.ToTable("user_tokens", (string)null);
+ });
+
+ modelBuilder.Entity("Tiku.Domain.Catalog.Category", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("id")
+ .HasDefaultValueSql("gen_random_uuid()");
+
+ b.Property("CategoryType")
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)")
+ .HasColumnName("category_type");
+
+ b.Property("CreatedAt")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at")
+ .HasDefaultValueSql("now()");
+
+ b.Property("IsActive")
+ .HasColumnType("boolean")
+ .HasColumnName("is_active");
+
+ b.Property("LegacyId")
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)")
+ .HasColumnName("legacy_id");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(300)
+ .HasColumnType("character varying(300)")
+ .HasColumnName("name");
+
+ b.Property("NodeId")
+ .HasColumnType("uuid")
+ .HasColumnName("node_id");
+
+ b.Property("SortOrder")
+ .HasColumnType("integer")
+ .HasColumnName("sort_order");
+
+ b.Property("SubjectId")
+ .HasColumnType("uuid")
+ .HasColumnName("subject_id");
+
+ b.Property("SvipQuestionLimit")
+ .HasColumnType("integer")
+ .HasColumnName("svip_question_limit");
+
+ b.Property("TenantId")
+ .HasColumnType("uuid")
+ .HasColumnName("tenant_id");
+
+ b.Property("UpdatedAt")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("updated_at")
+ .HasDefaultValueSql("now()");
+
+ b.HasKey("Id")
+ .HasName("pk_categories");
+
+ b.HasAlternateKey("TenantId", "Id")
+ .HasName("ak_categories_tenant_id_id");
+
+ b.HasIndex("TenantId", "LegacyId")
+ .IsUnique()
+ .HasDatabaseName("ix_categories_tenant_id_legacy_id");
+
+ b.HasIndex("TenantId", "NodeId")
+ .HasDatabaseName("ix_categories_tenant_id_node_id");
+
+ b.HasIndex("TenantId", "SubjectId")
+ .HasDatabaseName("ix_categories_tenant_id_subject_id");
+
+ b.ToTable("categories", (string)null);
+ });
+
+ modelBuilder.Entity("Tiku.Domain.Catalog.Major", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("id")
+ .HasDefaultValueSql("gen_random_uuid()");
+
+ b.Property("CreatedAt")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at")
+ .HasDefaultValueSql("now()");
+
+ b.Property("Description")
+ .HasColumnType("text")
+ .HasColumnName("description");
+
+ b.Property("IsActive")
+ .HasColumnType("boolean")
+ .HasColumnName("is_active");
+
+ b.Property("LegacyId")
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)")
+ .HasColumnName("legacy_id");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(300)
+ .HasColumnType("character varying(300)")
+ .HasColumnName("name");
+
+ b.Property("RegionId")
+ .HasColumnType("uuid")
+ .HasColumnName("region_id");
+
+ b.Property("SchoolId")
+ .HasColumnType("uuid")
+ .HasColumnName("school_id");
+
+ b.Property("SortOrder")
+ .HasColumnType("integer")
+ .HasColumnName("sort_order");
+
+ b.Property("StudyTips")
+ .HasColumnType("text")
+ .HasColumnName("study_tips");
+
+ b.Property("TenantId")
+ .HasColumnType("uuid")
+ .HasColumnName("tenant_id");
+
+ b.Property("UpdatedAt")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("updated_at")
+ .HasDefaultValueSql("now()");
+
+ b.HasKey("Id")
+ .HasName("pk_majors");
+
+ b.HasAlternateKey("TenantId", "Id")
+ .HasName("ak_majors_tenant_id_id");
+
+ b.HasIndex("TenantId", "LegacyId")
+ .IsUnique()
+ .HasDatabaseName("ix_majors_tenant_id_legacy_id");
+
+ b.HasIndex("TenantId", "RegionId")
+ .HasDatabaseName("ix_majors_tenant_id_region_id");
+
+ b.HasIndex("TenantId", "SchoolId")
+ .HasDatabaseName("ix_majors_tenant_id_school_id");
+
+ b.ToTable("majors", (string)null);
+ });
+
+ modelBuilder.Entity("Tiku.Domain.Catalog.ModuleNode", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("id")
+ .HasDefaultValueSql("gen_random_uuid()");
+
+ b.Property("CreatedAt")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at")
+ .HasDefaultValueSql("now()");
+
+ b.Property("IsActive")
+ .HasColumnType("boolean")
+ .HasColumnName("is_active");
+
+ b.Property("LegacyId")
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)")
+ .HasColumnName("legacy_id");
+
+ b.Property("LegacyModuleId")
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)")
+ .HasColumnName("legacy_module_id");
+
+ b.Property("LegacyParentId")
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)")
+ .HasColumnName("legacy_parent_id");
+
+ b.Property("Metadata")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("jsonb")
+ .HasColumnName("metadata")
+ .HasDefaultValueSql("'{}'::jsonb");
+
+ b.Property("ModuleId")
+ .HasColumnType("uuid")
+ .HasColumnName("module_id");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(300)
+ .HasColumnType("character varying(300)")
+ .HasColumnName("name");
+
+ b.Property("ParentId")
+ .HasColumnType("uuid")
+ .HasColumnName("parent_id");
+
+ b.Property("Path")
+ .HasMaxLength(1000)
+ .HasColumnType("character varying(1000)")
+ .HasColumnName("path");
+
+ b.Property("RegionId")
+ .HasColumnType("uuid")
+ .HasColumnName("region_id");
+
+ b.Property("SortOrder")
+ .HasColumnType("integer")
+ .HasColumnName("sort_order");
+
+ b.Property("TenantId")
+ .HasColumnType("uuid")
+ .HasColumnName("tenant_id");
+
+ b.Property("Type")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)")
+ .HasColumnName("type");
+
+ b.Property("UpdatedAt")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("updated_at")
+ .HasDefaultValueSql("now()");
+
+ b.HasKey("Id")
+ .HasName("pk_module_nodes");
+
+ b.HasAlternateKey("TenantId", "Id")
+ .HasName("ak_module_nodes_tenant_id_id");
+
+ b.HasIndex("TenantId", "LegacyId")
+ .IsUnique()
+ .HasDatabaseName("ix_module_nodes_tenant_id_legacy_id");
+
+ b.HasIndex("TenantId", "ModuleId")
+ .HasDatabaseName("ix_module_nodes_tenant_id_module_id");
+
+ b.HasIndex("TenantId", "ParentId")
+ .HasDatabaseName("ix_module_nodes_tenant_id_parent_id");
+
+ b.HasIndex("TenantId", "RegionId")
+ .HasDatabaseName("ix_module_nodes_tenant_id_region_id");
+
+ b.ToTable("module_nodes", (string)null);
+ });
+
+ modelBuilder.Entity("Tiku.Domain.Catalog.Region", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("id")
+ .HasDefaultValueSql("gen_random_uuid()");
+
+ b.Property("Code")
+ .HasMaxLength(50)
+ .HasColumnType("character varying(50)")
+ .HasColumnName("code");
+
+ b.Property("Config")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("jsonb")
+ .HasColumnName("config")
+ .HasDefaultValueSql("'{}'::jsonb");
+
+ b.Property("CreatedAt")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at")
+ .HasDefaultValueSql("now()");
+
+ b.Property("FullName")
+ .HasMaxLength(300)
+ .HasColumnType("character varying(300)")
+ .HasColumnName("full_name");
+
+ b.Property("Icon")
+ .HasMaxLength(2048)
+ .HasColumnType("character varying(2048)")
+ .HasColumnName("icon");
+
+ b.Property("IsActive")
+ .HasColumnType("boolean")
+ .HasColumnName("is_active");
+
+ b.Property("IsHot")
+ .HasColumnType("boolean")
+ .HasColumnName("is_hot");
+
+ b.Property("LegacyId")
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)")
+ .HasColumnName("legacy_id");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("character varying(200)")
+ .HasColumnName("name");
+
+ b.Property("Pinyin")
+ .HasMaxLength(255)
+ .HasColumnType("character varying(255)")
+ .HasColumnName("pinyin");
+
+ b.Property("ShortName")
+ .HasMaxLength(100)
+ .HasColumnType("character varying(100)")
+ .HasColumnName("short_name");
+
+ b.Property("SortOrder")
+ .HasColumnType("integer")
+ .HasColumnName("sort_order");
+
+ b.Property("TenantId")
+ .HasColumnType("uuid")
+ .HasColumnName("tenant_id");
+
+ b.Property("UpdatedAt")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("updated_at")
+ .HasDefaultValueSql("now()");
+
+ b.HasKey("Id")
+ .HasName("pk_regions");
+
+ b.HasAlternateKey("TenantId", "Id")
+ .HasName("ak_regions_tenant_id_id");
+
+ b.HasIndex("TenantId", "LegacyId")
+ .IsUnique()
+ .HasDatabaseName("ix_regions_tenant_id_legacy_id");
+
+ b.ToTable("regions", (string)null);
+ });
+
+ modelBuilder.Entity("Tiku.Domain.Catalog.RegionModule", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("id")
+ .HasDefaultValueSql("gen_random_uuid()");
+
+ b.Property("Color")
+ .HasMaxLength(50)
+ .HasColumnType("character varying(50)")
+ .HasColumnName("color");
+
+ b.Property("CreatedAt")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at")
+ .HasDefaultValueSql("now()");
+
+ b.Property("Description")
+ .HasColumnType("text")
+ .HasColumnName("description");
+
+ b.Property("Icon")
+ .HasMaxLength(2048)
+ .HasColumnType("character varying(2048)")
+ .HasColumnName("icon");
+
+ b.Property("IsActive")
+ .HasColumnType("boolean")
+ .HasColumnName("is_active");
+
+ b.Property("IsPrimarySchoolModule")
+ .HasColumnType("boolean")
+ .HasColumnName("is_primary_school_module");
+
+ b.Property("LegacyId")
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)")
+ .HasColumnName("legacy_id");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("character varying(200)")
+ .HasColumnName("name");
+
+ b.Property("RegionId")
+ .HasColumnType("uuid")
+ .HasColumnName("region_id");
+
+ b.Property("Route")
+ .HasMaxLength(500)
+ .HasColumnType("character varying(500)")
+ .HasColumnName("route");
+
+ b.Property("SortOrder")
+ .HasColumnType("integer")
+ .HasColumnName("sort_order");
+
+ b.Property("TenantId")
+ .HasColumnType("uuid")
+ .HasColumnName("tenant_id");
+
+ b.Property("TextColor")
+ .HasMaxLength(50)
+ .HasColumnType("character varying(50)")
+ .HasColumnName("text_color");
+
+ b.Property("Type")
+ .HasMaxLength(50)
+ .HasColumnType("character varying(50)")
+ .HasColumnName("type");
+
+ b.Property("UpdatedAt")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("updated_at")
+ .HasDefaultValueSql("now()");
+
+ b.HasKey("Id")
+ .HasName("pk_region_modules");
+
+ b.HasAlternateKey("TenantId", "Id")
+ .HasName("ak_region_modules_tenant_id_id");
+
+ b.HasIndex("TenantId", "LegacyId")
+ .IsUnique()
+ .HasDatabaseName("ix_region_modules_tenant_id_legacy_id");
+
+ b.HasIndex("TenantId", "RegionId")
+ .HasDatabaseName("ix_region_modules_tenant_id_region_id");
+
+ b.ToTable("region_modules", (string)null);
+ });
+
+ modelBuilder.Entity("Tiku.Domain.Catalog.School", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("id")
+ .HasDefaultValueSql("gen_random_uuid()");
+
+ b.Property("CreatedAt")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at")
+ .HasDefaultValueSql("now()");
+
+ b.Property("LegacyId")
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)")
+ .HasColumnName("legacy_id");
+
+ b.Property("Metadata")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("jsonb")
+ .HasColumnName("metadata")
+ .HasDefaultValueSql("'{}'::jsonb");
+
+ b.Property("ModuleId")
+ .HasColumnType("uuid")
+ .HasColumnName("module_id");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(300)
+ .HasColumnType("character varying(300)")
+ .HasColumnName("name");
+
+ b.Property("ProfessionalExamDate")
+ .HasMaxLength(255)
+ .HasColumnType("character varying(255)")
+ .HasColumnName("professional_exam_date");
+
+ b.Property("RegionId")
+ .HasColumnType("uuid")
+ .HasColumnName("region_id");
+
+ b.Property("TenantId")
+ .HasColumnType("uuid")
+ .HasColumnName("tenant_id");
+
+ b.Property("UpdatedAt")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("updated_at")
+ .HasDefaultValueSql("now()");
+
+ b.HasKey("Id")
+ .HasName("pk_schools");
+
+ b.HasAlternateKey("TenantId", "Id")
+ .HasName("ak_schools_tenant_id_id");
+
+ b.HasIndex("TenantId", "LegacyId")
+ .IsUnique()
+ .HasDatabaseName("ix_schools_tenant_id_legacy_id");
+
+ b.HasIndex("TenantId", "ModuleId")
+ .HasDatabaseName("ix_schools_tenant_id_module_id");
+
+ b.HasIndex("TenantId", "RegionId")
+ .HasDatabaseName("ix_schools_tenant_id_region_id");
+
+ b.ToTable("schools", (string)null);
+ });
+
+ modelBuilder.Entity("Tiku.Domain.Catalog.ScorelineField", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("id")
+ .HasDefaultValueSql("gen_random_uuid()");
+
+ b.Property("CreatedAt")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at")
+ .HasDefaultValueSql("now()");
+
+ b.Property("Description")
+ .HasMaxLength(1000)
+ .HasColumnType("character varying(1000)")
+ .HasColumnName("description");
+
+ b.Property("FieldKey")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)")
+ .HasColumnName("field_key");
+
+ b.Property("FieldName")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("character varying(200)")
+ .HasColumnName("field_name");
+
+ b.Property("FieldType")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)")
+ .HasColumnName("field_type");
+
+ b.Property("IsFilter")
+ .HasColumnType("boolean")
+ .HasColumnName("is_filter");
+
+ b.Property("IsRequired")
+ .HasColumnType("boolean")
+ .HasColumnName("is_required");
+
+ b.Property("IsTrend")
+ .HasColumnType("boolean")
+ .HasColumnName("is_trend");
+
+ b.Property("IsVisible")
+ .HasColumnType("boolean")
+ .HasColumnName("is_visible");
+
+ b.Property("LegacyId")
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)")
+ .HasColumnName("legacy_id");
+
+ b.Property("Options")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("jsonb")
+ .HasColumnName("options")
+ .HasDefaultValueSql("'[]'::jsonb");
+
+ b.Property("Placeholder")
+ .HasMaxLength(200)
+ .HasColumnType("character varying(200)")
+ .HasColumnName("placeholder");
+
+ b.Property("RegionId")
+ .HasColumnType("uuid")
+ .HasColumnName("region_id");
+
+ b.Property("SortOrder")
+ .HasColumnType("integer")
+ .HasColumnName("sort_order");
+
+ b.Property("TenantId")
+ .HasColumnType("uuid")
+ .HasColumnName("tenant_id");
+
+ b.Property("Unit")
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)")
+ .HasColumnName("unit");
+
+ b.Property("UpdatedAt")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("updated_at")
+ .HasDefaultValueSql("now()");
+
+ b.HasKey("Id")
+ .HasName("pk_scoreline_fields");
+
+ b.HasAlternateKey("TenantId", "Id")
+ .HasName("ak_scoreline_fields_tenant_id_id");
+
+ b.HasIndex("TenantId", "LegacyId")
+ .IsUnique()
+ .HasDatabaseName("ix_scoreline_fields_tenant_id_legacy_id");
+
+ b.HasIndex("TenantId", "RegionId", "FieldKey")
+ .IsUnique()
+ .HasDatabaseName("ix_scoreline_fields_tenant_id_region_id_field_key");
+
+ b.HasIndex("TenantId", "RegionId", "IsFilter", "SortOrder")
+ .HasDatabaseName("ix_scoreline_fields_tenant_id_region_id_is_filter_sort_order");
+
+ b.ToTable("scoreline_fields", (string)null);
+ });
+
+ modelBuilder.Entity("Tiku.Domain.Catalog.ScorelineRecord", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("id")
+ .HasDefaultValueSql("gen_random_uuid()");
+
+ b.Property("CreatedAt")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at")
+ .HasDefaultValueSql("now()");
+
+ b.Property("FieldValues")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("jsonb")
+ .HasColumnName("field_values")
+ .HasDefaultValueSql("'{}'::jsonb");
+
+ b.Property("LegacyId")
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)")
+ .HasColumnName("legacy_id");
+
+ b.Property("MajorId")
+ .HasColumnType("uuid")
+ .HasColumnName("major_id");
+
+ b.Property("MajorName")
+ .HasMaxLength(300)
+ .HasColumnType("character varying(300)")
+ .HasColumnName("major_name");
+
+ b.Property("RegionId")
+ .HasColumnType("uuid")
+ .HasColumnName("region_id");
+
+ b.Property("SchoolId")
+ .HasColumnType("uuid")
+ .HasColumnName("school_id");
+
+ b.Property("SchoolName")
+ .HasMaxLength(300)
+ .HasColumnType("character varying(300)")
+ .HasColumnName("school_name");
+
+ b.Property("TenantId")
+ .HasColumnType("uuid")
+ .HasColumnName("tenant_id");
+
+ b.Property("UpdatedAt")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("updated_at")
+ .HasDefaultValueSql("now()");
+
+ b.Property("Year")
+ .HasColumnType("integer")
+ .HasColumnName("year");
+
+ b.HasKey("Id")
+ .HasName("pk_scoreline_records");
+
+ b.HasAlternateKey("TenantId", "Id")
+ .HasName("ak_scoreline_records_tenant_id_id");
+
+ b.HasIndex("TenantId", "LegacyId")
+ .IsUnique()
+ .HasDatabaseName("ix_scoreline_records_tenant_id_legacy_id");
+
+ b.HasIndex("TenantId", "MajorId")
+ .HasDatabaseName("ix_scoreline_records_tenant_id_major_id");
+
+ b.HasIndex("TenantId", "SchoolId")
+ .HasDatabaseName("ix_scoreline_records_tenant_id_school_id");
+
+ b.HasIndex("TenantId", "Year")
+ .HasDatabaseName("ix_scoreline_records_tenant_id_year");
+
+ b.HasIndex("TenantId", "RegionId", "SchoolId", "MajorId", "Year")
+ .HasDatabaseName("ix_scoreline_records_tenant_id_region_id_school_id_major_id_ye~");
+
+ b.HasIndex("TenantId", "Year", "SchoolName", "MajorName", "Id")
+ .IsDescending(false, true, false, false, false)
+ .HasDatabaseName("ix_scoreline_records_tenant_id_year_school_name_major_name_id");
+
+ b.ToTable("scoreline_records", (string)null);
+ });
+
+ modelBuilder.Entity("Tiku.Domain.Catalog.Subject", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("id")
+ .HasDefaultValueSql("gen_random_uuid()");
+
+ b.Property("CreatedAt")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at")
+ .HasDefaultValueSql("now()");
+
+ b.Property("Description")
+ .HasColumnType("text")
+ .HasColumnName("description");
+
+ b.Property("Icon")
+ .HasMaxLength(2048)
+ .HasColumnType("character varying(2048)")
+ .HasColumnName("icon");
+
+ b.Property("IsActive")
+ .HasColumnType("boolean")
+ .HasColumnName("is_active");
+
+ b.Property("LegacyId")
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)")
+ .HasColumnName("legacy_id");
+
+ b.Property("MajorId")
+ .HasColumnType("uuid")
+ .HasColumnName("major_id");
+
+ b.Property("MajorLegacyIds")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("jsonb")
+ .HasColumnName("major_legacy_ids")
+ .HasDefaultValueSql("'[]'::jsonb");
+
+ b.Property("ModuleId")
+ .HasColumnType("uuid")
+ .HasColumnName("module_id");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(300)
+ .HasColumnType("character varying(300)")
+ .HasColumnName("name");
+
+ b.Property("NodeId")
+ .HasColumnType("uuid")
+ .HasColumnName("node_id");
+
+ b.Property("RegionId")
+ .HasColumnType("uuid")
+ .HasColumnName("region_id");
+
+ b.Property("SchoolId")
+ .HasColumnType("uuid")
+ .HasColumnName("school_id");
+
+ b.Property("SortOrder")
+ .HasColumnType("integer")
+ .HasColumnName("sort_order");
+
+ b.Property("Stats")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("jsonb")
+ .HasColumnName("stats")
+ .HasDefaultValueSql("'{}'::jsonb");
+
+ b.Property("TenantId")
+ .HasColumnType("uuid")
+ .HasColumnName("tenant_id");
+
+ b.Property("Type")
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)")
+ .HasColumnName("type");
+
+ b.Property("UpdatedAt")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("updated_at")
+ .HasDefaultValueSql("now()");
+
+ b.HasKey("Id")
+ .HasName("pk_subjects");
+
+ b.HasAlternateKey("TenantId", "Id")
+ .HasName("ak_subjects_tenant_id_id");
+
+ b.HasIndex("TenantId", "LegacyId")
+ .IsUnique()
+ .HasDatabaseName("ix_subjects_tenant_id_legacy_id");
+
+ b.HasIndex("TenantId", "MajorId")
+ .HasDatabaseName("ix_subjects_tenant_id_major_id");
+
+ b.HasIndex("TenantId", "ModuleId")
+ .HasDatabaseName("ix_subjects_tenant_id_module_id");
+
+ b.HasIndex("TenantId", "NodeId")
+ .HasDatabaseName("ix_subjects_tenant_id_node_id");
+
+ b.HasIndex("TenantId", "RegionId")
+ .HasDatabaseName("ix_subjects_tenant_id_region_id");
+
+ b.HasIndex("TenantId", "SchoolId")
+ .HasDatabaseName("ix_subjects_tenant_id_school_id");
+
+ b.ToTable("subjects", (string)null);
+ });
+
+ modelBuilder.Entity("Tiku.Domain.Commerce.ActivationCode", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("id")
+ .HasDefaultValueSql("gen_random_uuid()");
+
+ b.Property("AgentUserId")
+ .HasColumnType("uuid")
+ .HasColumnName("agent_user_id");
+
+ b.Property("BatchId")
+ .HasColumnType("uuid")
+ .HasColumnName("batch_id");
+
+ b.Property("Code")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("citext")
+ .HasColumnName("code");
+
+ b.Property("CouponCode")
+ .HasMaxLength(100)
+ .HasColumnType("character varying(100)")
+ .HasColumnName("coupon_code");
+
+ b.Property("CouponRedemptionId")
+ .HasColumnType("uuid")
+ .HasColumnName("coupon_redemption_id");
+
+ b.Property("CreatedAt")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at")
+ .HasDefaultValueSql("now()");
+
+ b.Property("Days")
+ .HasColumnType("integer")
+ .HasColumnName("days");
+
+ b.Property("IsUsed")
+ .HasColumnType("boolean")
+ .HasColumnName("is_used");
+
+ b.Property("LegacyId")
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)")
+ .HasColumnName("legacy_id");
+
+ b.Property("Remark")
+ .HasColumnType("text")
+ .HasColumnName("remark");
+
+ b.Property("SaleType")
+ .HasMaxLength(50)
+ .HasColumnType("character varying(50)")
+ .HasColumnName("sale_type");
+
+ b.Property("SoldTo")
+ .HasMaxLength(200)
+ .HasColumnType("character varying(200)")
+ .HasColumnName("sold_to");
+
+ b.Property("TenantId")
+ .HasColumnType("uuid")
+ .HasColumnName("tenant_id");
+
+ b.Property("UnitPriceCents")
+ .HasColumnType("integer")
+ .HasColumnName("unit_price_cents");
+
+ b.Property("UpdatedAt")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("updated_at")
+ .HasDefaultValueSql("now()");
+
+ b.Property("UsedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("used_at");
+
+ b.Property("UsedBy")
+ .HasColumnType("uuid")
+ .HasColumnName("used_by");
+
+ b.Property("UsedRegionId")
+ .HasColumnType("uuid")
+ .HasColumnName("used_region_id");
+
+ b.HasKey("Id")
+ .HasName("pk_activation_codes");
+
+ b.HasAlternateKey("TenantId", "Id")
+ .HasName("ak_activation_codes_tenant_id_id");
+
+ b.HasIndex("AgentUserId")
+ .HasDatabaseName("ix_activation_codes_agent_user_id");
+
+ b.HasIndex("UsedBy")
+ .HasDatabaseName("ix_activation_codes_used_by");
+
+ b.HasIndex("TenantId", "BatchId")
+ .HasDatabaseName("ix_activation_codes_tenant_id_batch_id");
+
+ b.HasIndex("TenantId", "Code")
+ .IsUnique()
+ .HasDatabaseName("ix_activation_codes_tenant_id_code");
+
+ b.HasIndex("TenantId", "CouponRedemptionId")
+ .HasDatabaseName("ix_activation_codes_tenant_id_coupon_redemption_id");
+
+ b.HasIndex("TenantId", "LegacyId")
+ .IsUnique()
+ .HasDatabaseName("ix_activation_codes_tenant_id_legacy_id");
+
+ b.HasIndex("TenantId", "UsedRegionId")
+ .HasDatabaseName("ix_activation_codes_tenant_id_used_region_id");
+
+ b.ToTable("activation_codes", (string)null);
+ });
+
+ modelBuilder.Entity("Tiku.Domain.Commerce.CodeBatch", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("id")
+ .HasDefaultValueSql("gen_random_uuid()");
+
+ b.Property("CampaignName")
+ .HasMaxLength(200)
+ .HasColumnType("character varying(200)")
+ .HasColumnName("campaign_name");
+
+ b.Property("Channel")
+ .HasMaxLength(100)
+ .HasColumnType("character varying(100)")
+ .HasColumnName("channel");
+
+ b.Property("CommissionRate")
+ .HasPrecision(6, 4)
+ .HasColumnType("numeric(6,4)")
+ .HasColumnName("commission_rate");
+
+ b.Property("CostPriceCents")
+ .HasColumnType("integer")
+ .HasColumnName("cost_price_cents");
+
+ b.Property("CreatedAt")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at")
+ .HasDefaultValueSql("now()");
+
+ b.Property("CreatedBy")
+ .HasColumnType("uuid")
+ .HasColumnName("created_by");
+
+ b.Property("Days")
+ .HasColumnType("integer")
+ .HasColumnName("days");
+
+ b.Property("DefaultUnitPriceCents")
+ .HasColumnType("integer")
+ .HasColumnName("default_unit_price_cents");
+
+ b.Property("IssuedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("issued_at");
+
+ b.Property("LegacyId")
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)")
+ .HasColumnName("legacy_id");
+
+ b.Property("LegacyRegionId")
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)")
+ .HasColumnName("legacy_region_id");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("character varying(200)")
+ .HasColumnName("name");
+
+ b.Property("RegionId")
+ .HasColumnType("uuid")
+ .HasColumnName("region_id");
+
+ b.Property("Remark")
+ .HasColumnType("text")
+ .HasColumnName("remark");
+
+ b.Property("SaleType")
+ .HasMaxLength(50)
+ .HasColumnType("character varying(50)")
+ .HasColumnName("sale_type");
+
+ b.Property("TenantId")
+ .HasColumnType("uuid")
+ .HasColumnName("tenant_id");
+
+ b.Property("TotalCount")
+ .HasColumnType("integer")
+ .HasColumnName("total_count");
+
+ b.Property("UpdatedAt")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("updated_at")
+ .HasDefaultValueSql("now()");
+
+ b.HasKey("Id")
+ .HasName("pk_code_batches");
+
+ b.HasAlternateKey("TenantId", "Id")
+ .HasName("ak_code_batches_tenant_id_id");
+
+ b.HasIndex("TenantId", "LegacyId")
+ .IsUnique()
+ .HasDatabaseName("ix_code_batches_tenant_id_legacy_id");
+
+ b.ToTable("code_batches", null, t =>
+ {
+ t.HasCheckConstraint("ck_code_batches_amounts", "default_unit_price_cents >= 0 and cost_price_cents >= 0");
+
+ t.HasCheckConstraint("ck_code_batches_counts", "total_count >= 0");
+ });
+ });
+
+ modelBuilder.Entity("Tiku.Domain.Commerce.CommerceAdjustmentVoucher", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("id")
+ .HasDefaultValueSql("gen_random_uuid()");
+
+ b.Property("AmountCents")
+ .HasColumnType("integer")
+ .HasColumnName("amount_cents");
+
+ b.Property("BatchId")
+ .HasColumnType("uuid")
+ .HasColumnName("batch_id");
+
+ b.Property("ClosedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("closed_at");
+
+ b.Property("CreatedAt")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at")
+ .HasDefaultValueSql("now()");
+
+ b.Property("CreatedBy")
+ .HasColumnType("uuid")
+ .HasColumnName("created_by");
+
+ b.Property("Currency")
+ .IsRequired()
+ .HasMaxLength(10)
+ .HasColumnType("character varying(10)")
+ .HasColumnName("currency");
+
+ b.Property("Direction")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)")
+ .HasColumnName("direction");
+
+ b.Property("IssueId")
+ .HasColumnType("uuid")
+ .HasColumnName("issue_id");
+
+ b.Property("ItemId")
+ .HasColumnType("uuid")
+ .HasColumnName("item_id");
+
+ b.Property("Metadata")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("jsonb")
+ .HasColumnName("metadata")
+ .HasDefaultValueSql("'{}'::jsonb");
+
+ b.Property("OrderId")
+ .HasColumnType("uuid")
+ .HasColumnName("order_id");
+
+ b.Property("PaymentId")
+ .HasColumnType("uuid")
+ .HasColumnName("payment_id");
+
+ b.Property("ProofAssetKey")
+ .HasMaxLength(500)
+ .HasColumnType("character varying(500)")
+ .HasColumnName("proof_asset_key");
+
+ b.Property("Reason")
+ .IsRequired()
+ .HasMaxLength(1000)
+ .HasColumnType("character varying(1000)")
+ .HasColumnName("reason");
+
+ b.Property("RefundRequestId")
+ .HasColumnType("uuid")
+ .HasColumnName("refund_request_id");
+
+ b.Property("ReviewedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("reviewed_at");
+
+ b.Property("ReviewedBy")
+ .HasColumnType("uuid")
+ .HasColumnName("reviewed_by");
+
+ b.Property("Status")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)")
+ .HasColumnName("status");
+
+ b.Property("TenantId")
+ .HasColumnType("uuid")
+ .HasColumnName("tenant_id");
+
+ b.Property("UpdatedAt")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("updated_at")
+ .HasDefaultValueSql("now()");
+
+ b.Property("VoucherNo")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("character varying(100)")
+ .HasColumnName("voucher_no");
+
+ b.HasKey("Id")
+ .HasName("pk_commerce_adjustment_vouchers");
+
+ b.HasAlternateKey("TenantId", "Id")
+ .HasName("ak_commerce_adjustment_vouchers_tenant_id_id");
+
+ b.HasIndex("CreatedBy")
+ .HasDatabaseName("ix_commerce_adjustment_vouchers_created_by");
+
+ b.HasIndex("ReviewedBy")
+ .HasDatabaseName("ix_commerce_adjustment_vouchers_reviewed_by");
+
+ b.HasIndex("TenantId", "BatchId")
+ .HasDatabaseName("ix_commerce_adjustment_vouchers_tenant_id_batch_id");
+
+ b.HasIndex("TenantId", "ItemId")
+ .HasDatabaseName("ix_commerce_adjustment_vouchers_tenant_id_item_id");
+
+ b.HasIndex("TenantId", "OrderId")
+ .HasDatabaseName("ix_commerce_adjustment_vouchers_tenant_id_order_id");
+
+ b.HasIndex("TenantId", "PaymentId")
+ .HasDatabaseName("ix_commerce_adjustment_vouchers_tenant_id_payment_id");
+
+ b.HasIndex("TenantId", "RefundRequestId")
+ .HasDatabaseName("ix_commerce_adjustment_vouchers_tenant_id_refund_request_id");
+
+ b.HasIndex("TenantId", "VoucherNo")
+ .IsUnique()
+ .HasDatabaseName("ix_commerce_adjustment_vouchers_tenant_id_voucher_no");
+
+ b.HasIndex("TenantId", "IssueId", "CreatedAt")
+ .HasDatabaseName("ix_commerce_adjustment_vouchers_tenant_id_issue_id_created_at");
+
+ b.HasIndex("TenantId", "Status", "CreatedAt")
+ .HasDatabaseName("ix_commerce_adjustment_vouchers_tenant_id_status_created_at");
+
+ b.ToTable("commerce_adjustment_vouchers", null, t =>
+ {
+ t.HasCheckConstraint("ck_commerce_adjustment_vouchers_amount", "amount_cents > 0");
+ });
+ });
+
+ modelBuilder.Entity("Tiku.Domain.Commerce.CommerceAdjustmentVoucherEvent", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("id")
+ .HasDefaultValueSql("gen_random_uuid()");
+
+ b.Property("ActorUserId")
+ .HasColumnType("uuid")
+ .HasColumnName("actor_user_id");
+
+ b.Property("CreatedAt")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at")
+ .HasDefaultValueSql("now()");
+
+ b.Property("Details")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("jsonb")
+ .HasColumnName("details")
+ .HasDefaultValueSql("'{}'::jsonb");
+
+ b.Property("FromStatus")
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)")
+ .HasColumnName("from_status");
+
+ b.Property("Note")
+ .HasMaxLength(1000)
+ .HasColumnType("character varying(1000)")
+ .HasColumnName("note");
+
+ b.Property("TenantId")
+ .HasColumnType("uuid")
+ .HasColumnName("tenant_id");
+
+ b.Property("ToStatus")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)")
+ .HasColumnName("to_status");
+
+ b.Property("VoucherId")
+ .HasColumnType("uuid")
+ .HasColumnName("voucher_id");
+
+ b.HasKey("Id")
+ .HasName("pk_commerce_adjustment_voucher_events");
+
+ b.HasAlternateKey("TenantId", "Id")
+ .HasName("ak_commerce_adjustment_voucher_events_tenant_id_id");
+
+ b.HasIndex("ActorUserId")
+ .HasDatabaseName("ix_commerce_adjustment_voucher_events_actor_user_id");
+
+ b.HasIndex("TenantId", "VoucherId", "CreatedAt")
+ .HasDatabaseName("ix_commerce_adjustment_voucher_events_tenant_id_voucher_id_cre~");
+
+ b.ToTable("commerce_adjustment_voucher_events", (string)null);
+ });
+
+ modelBuilder.Entity("Tiku.Domain.Commerce.CommerceReconciliationBatch", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("id")
+ .HasDefaultValueSql("gen_random_uuid()");
+
+ b.Property("AmountCents")
+ .HasColumnType("integer")
+ .HasColumnName("amount_cents");
+
+ b.Property("BillDate")
+ .HasColumnType("date")
+ .HasColumnName("bill_date");
+
+ b.Property("BillType")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)")
+ .HasColumnName("bill_type");
+
+ b.Property("CompletedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("completed_at");
+
+ b.Property("CreatedAt")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at")
+ .HasDefaultValueSql("now()");
+
+ b.Property("CreatedBy")
+ .HasColumnType("uuid")
+ .HasColumnName("created_by");
+
+ b.Property("DuplicateCount")
+ .HasColumnType("integer")
+ .HasColumnName("duplicate_count");
+
+ b.Property("Error")
+ .HasColumnType("text")
+ .HasColumnName("error");
+
+ b.Property("FeeCents")
+ .HasColumnType("integer")
+ .HasColumnName("fee_cents");
+
+ b.Property("IgnoredCount")
+ .HasColumnType("integer")
+ .HasColumnName("ignored_count");
+
+ b.Property("MatchedCount")
+ .HasColumnType("integer")
+ .HasColumnName("matched_count");
+
+ b.Property("Metadata")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("jsonb")
+ .HasColumnName("metadata")
+ .HasDefaultValueSql("'{}'::jsonb");
+
+ b.Property("MismatchCount")
+ .HasColumnType("integer")
+ .HasColumnName("mismatch_count");
+
+ b.Property("MissingLocalCount")
+ .HasColumnType("integer")
+ .HasColumnName("missing_local_count");
+
+ b.Property("MissingProviderCount")
+ .HasColumnType("integer")
+ .HasColumnName("missing_provider_count");
+
+ b.Property("Provider")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("character varying(50)")
+ .HasColumnName("provider");
+
+ b.Property("RefundAmountCents")
+ .HasColumnType("integer")
+ .HasColumnName("refund_amount_cents");
+
+ b.Property("Source")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)")
+ .HasColumnName("source");
+
+ b.Property("SourceHash")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("character varying(200)")
+ .HasColumnName("source_hash");
+
+ b.Property("SourceName")
+ .HasMaxLength(300)
+ .HasColumnType("character varying(300)")
+ .HasColumnName("source_name");
+
+ b.Property("Status")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)")
+ .HasColumnName("status");
+
+ b.Property("TenantId")
+ .HasColumnType("uuid")
+ .HasColumnName("tenant_id");
+
+ b.Property("TotalCount")
+ .HasColumnType("integer")
+ .HasColumnName("total_count");
+
+ b.Property("UpdatedAt")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("updated_at")
+ .HasDefaultValueSql("now()");
+
+ b.HasKey("Id")
+ .HasName("pk_commerce_reconciliation_batches");
+
+ b.HasAlternateKey("TenantId", "Id")
+ .HasName("ak_commerce_reconciliation_batches_tenant_id_id");
+
+ b.HasIndex("CreatedBy")
+ .HasDatabaseName("ix_commerce_reconciliation_batches_created_by");
+
+ b.HasIndex("TenantId", "Status", "CreatedAt")
+ .HasDatabaseName("ix_commerce_reconciliation_batches_tenant_id_status_created_at");
+
+ b.HasIndex("TenantId", "Provider", "BillDate", "CreatedAt")
+ .HasDatabaseName("ix_commerce_reconciliation_batches_tenant_id_provider_bill_dat~");
+
+ b.ToTable("commerce_reconciliation_batches", null, t =>
+ {
+ t.HasCheckConstraint("ck_commerce_reconciliation_batches_amounts", "amount_cents >= 0 and refund_amount_cents >= 0");
+
+ t.HasCheckConstraint("ck_commerce_reconciliation_batches_counts", "total_count >= 0 and matched_count >= 0 and mismatch_count >= 0 and missing_local_count >= 0 and missing_provider_count >= 0 and duplicate_count >= 0 and ignored_count >= 0");
+ });
+ });
+
+ modelBuilder.Entity("Tiku.Domain.Commerce.CommerceReconciliationIssue", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("id")
+ .HasDefaultValueSql("gen_random_uuid()");
+
+ b.Property("AmountCents")
+ .HasColumnType("integer")
+ .HasColumnName("amount_cents");
+
+ b.Property