diff --git a/Tiku.Api/Contracts/ProfileDtos.cs b/Tiku.Api/Contracts/ProfileDtos.cs
index f0a620f..de7c2c6 100644
--- a/Tiku.Api/Contracts/ProfileDtos.cs
+++ b/Tiku.Api/Contracts/ProfileDtos.cs
@@ -159,9 +159,9 @@ public sealed class NotificationStatusDto
public sealed class SubmitFeedbackDto
{
///
- /// 题目 ID。
+ /// 本次练习中实际交付的会话题目 ID。
///
- public Guid? QuestionId { get; set; }
+ public Guid? SessionQuestionId { get; set; }
///
/// 类型。
@@ -213,7 +213,7 @@ public sealed class SubmitFeedbackDto
public SubmitFeedbackCommand ToCommand()
{
return new SubmitFeedbackCommand(
- QuestionId,
+ SessionQuestionId,
Type,
Category,
Title,
@@ -223,4 +223,4 @@ public sealed class SubmitFeedbackDto
Attachments,
Metadata);
}
-}
\ No newline at end of file
+}
diff --git a/Tiku.Api/Modules/Student/Content/Errors/ContentExceptionProblemDetailsMapper.cs b/Tiku.Api/Modules/Student/Content/Errors/ContentExceptionProblemDetailsMapper.cs
index 33d023c..fdd4009 100644
--- a/Tiku.Api/Modules/Student/Content/Errors/ContentExceptionProblemDetailsMapper.cs
+++ b/Tiku.Api/Modules/Student/Content/Errors/ContentExceptionProblemDetailsMapper.cs
@@ -80,6 +80,7 @@ internal sealed class ContentExceptionProblemDetailsMapper : ExceptionProblemDet
"profile_access_denied" => StatusCodes.Status403Forbidden,
"profile_user_not_found" or "check_in_task_not_found" or "region_not_found" or "school_not_found" or
"major_not_found" => StatusCodes.Status404NotFound,
+ _ when code.EndsWith("_not_found", StringComparison.Ordinal) => StatusCodes.Status404NotFound,
_ => StatusCodes.Status400BadRequest
};
diff --git a/Tiku.Application/Profile/ProfileModels.cs b/Tiku.Application/Profile/ProfileModels.cs
index 2598a46..7dd7e4b 100644
--- a/Tiku.Application/Profile/ProfileModels.cs
+++ b/Tiku.Application/Profile/ProfileModels.cs
@@ -126,7 +126,11 @@ public sealed record FeedbackQuery(string? Status = null, string? Type = null, i
public sealed record FeedbackItem(
Guid Id,
- Guid? QuestionId,
+ Guid? SessionQuestionId,
+ Guid? QuestionAssetOwnerTenantId,
+ Guid? QuestionAssetId,
+ Guid? QuestionRevisionId,
+ Guid? QuestionPlacementId,
string? Type,
string? Title,
string? Category,
@@ -140,7 +144,7 @@ public sealed record FeedbackItem(
DateTimeOffset UpdatedAt);
public sealed record SubmitFeedbackCommand(
- Guid? QuestionId,
+ Guid? SessionQuestionId,
string? Type,
string? Category,
string? Title,
@@ -199,4 +203,4 @@ public interface IProfileService
Task GetScoreEventsAsync(ProfileActor actor, ProfileScoreEventQuery query,
CancellationToken cancellationToken = default);
-}
\ No newline at end of file
+}
diff --git a/Tiku.Application/TenantAdmin/TenantAdminDirectModels.cs b/Tiku.Application/TenantAdmin/TenantAdminDirectModels.cs
index 48826b5..ec7b680 100644
--- a/Tiku.Application/TenantAdmin/TenantAdminDirectModels.cs
+++ b/Tiku.Application/TenantAdmin/TenantAdminDirectModels.cs
@@ -610,7 +610,11 @@ public sealed record TenantAdminFeedbackItem(
Guid? UserId,
string? UserName,
string? UserPhone,
- Guid? QuestionId,
+ Guid? SessionQuestionId,
+ Guid? QuestionAssetOwnerTenantId,
+ Guid? QuestionAssetId,
+ Guid? QuestionRevisionId,
+ Guid? QuestionPlacementId,
ReportType? Type,
string? Title,
string? Category,
@@ -629,4 +633,4 @@ public sealed record TenantAdminFeedbackItem(
public sealed class TenantAdminDirectException(string message, string code) : Exception(message)
{
public string Code { get; } = code;
-}
\ No newline at end of file
+}
diff --git a/Tiku.Domain/Learning/LearningReportEntities.cs b/Tiku.Domain/Learning/LearningReportEntities.cs
index 580355e..703d23e 100644
--- a/Tiku.Domain/Learning/LearningReportEntities.cs
+++ b/Tiku.Domain/Learning/LearningReportEntities.cs
@@ -19,7 +19,11 @@ public sealed class ExamDate : AuditableTenantEntity
public sealed class Report : AuditableTenantEntity
{
- public Guid? QuestionId { get; set; }
+ public Guid? SessionQuestionId { get; set; }
+ public Guid? QuestionAssetOwnerTenantId { get; set; }
+ public Guid? QuestionAssetId { get; set; }
+ public Guid? QuestionRevisionId { get; set; }
+ public Guid? QuestionPlacementId { get; set; }
public Guid? UserId { get; set; }
public Guid? HandledBy { get; set; }
public string? LegacyId { get; set; }
diff --git a/Tiku.Infrastructure/Persistence/Configurations/LearningReportConfigurations.cs b/Tiku.Infrastructure/Persistence/Configurations/LearningReportConfigurations.cs
index 84709b7..bcd9b81 100644
--- a/Tiku.Infrastructure/Persistence/Configurations/LearningReportConfigurations.cs
+++ b/Tiku.Infrastructure/Persistence/Configurations/LearningReportConfigurations.cs
@@ -4,7 +4,6 @@ using Tiku.Domain.Catalog;
using Tiku.Domain.Content;
using Tiku.Domain.Identity;
using Tiku.Domain.Learning;
-using Tiku.Domain.QuestionBanks;
using Tiku.Domain.Tenancy;
namespace Tiku.Infrastructure.Persistence.Configurations;
@@ -50,11 +49,48 @@ internal sealed class ReportConfiguration : IEntityTypeConfiguration
builder.Property(entity => entity.Metadata).IsJson("{}");
builder.HasIndex(entity => new { entity.TenantId, entity.LegacyId }).IsUnique();
builder.HasIndex(entity => new { entity.TenantId, entity.Status, entity.CreatedAt });
- builder.HasIndex(entity => new { entity.TenantId, entity.QuestionId, entity.CreatedAt })
- .HasFilter("question_id is not null");
+ builder.HasIndex(entity => new { entity.TenantId, entity.SessionQuestionId, entity.CreatedAt })
+ .HasDatabaseName("ix_reports_session_question")
+ .HasFilter("session_question_id is not null");
+ builder.HasIndex(entity => new
+ {
+ entity.QuestionAssetOwnerTenantId,
+ entity.QuestionAssetId,
+ entity.QuestionRevisionId
+ }).HasDatabaseName("ix_reports_question_revision");
+ builder.HasIndex(entity => new
+ {
+ entity.QuestionAssetOwnerTenantId,
+ entity.QuestionPlacementId
+ }).HasDatabaseName("ix_reports_question_placement");
- builder.HasOne().WithMany()
- .HasForeignKey(entity => new { entity.TenantId, entity.QuestionId })
+ builder.HasOne().WithMany()
+ .HasForeignKey(entity => new { entity.TenantId, Id = entity.SessionQuestionId })
+ .HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
+ .OnDelete(DeleteBehavior.Restrict);
+ builder.HasOne().WithMany()
+ .HasForeignKey(entity => new
+ {
+ TenantId = entity.QuestionAssetOwnerTenantId,
+ Id = entity.QuestionAssetId
+ })
+ .HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
+ .OnDelete(DeleteBehavior.Restrict);
+ builder.HasOne().WithMany()
+ .HasForeignKey(entity => new
+ {
+ TenantId = entity.QuestionAssetOwnerTenantId,
+ QuestionAssetId = entity.QuestionAssetId,
+ Id = entity.QuestionRevisionId
+ })
+ .HasPrincipalKey(entity => new { entity.TenantId, entity.QuestionAssetId, entity.Id })
+ .OnDelete(DeleteBehavior.Restrict);
+ builder.HasOne().WithMany()
+ .HasForeignKey(entity => new
+ {
+ TenantId = entity.QuestionAssetOwnerTenantId,
+ Id = entity.QuestionPlacementId
+ })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne().WithMany()
diff --git a/Tiku.Infrastructure/Persistence/Migrations/20260806024912_MigrateFeedbackEvidenceV2.Designer.cs b/Tiku.Infrastructure/Persistence/Migrations/20260806024912_MigrateFeedbackEvidenceV2.Designer.cs
new file mode 100644
index 0000000..9c39e03
--- /dev/null
+++ b/Tiku.Infrastructure/Persistence/Migrations/20260806024912_MigrateFeedbackEvidenceV2.Designer.cs
@@ -0,0 +1,26511 @@
+//
+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("20260806024912_MigrateFeedbackEvidenceV2")]
+ partial class MigrateFeedbackEvidenceV2
+ {
+ ///
+ 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