feat: add learning reports and practice access persistence

This commit is contained in:
xiong
2026-07-26 05:24:55 +08:00
parent c217db4597
commit b2282cde8c
11 changed files with 9606 additions and 2 deletions

View File

@@ -58,6 +58,7 @@ public sealed class ContentNode : AuditableTenantEntity
public bool IsActive { get; set; } = true;
public bool IsSelectable { get; set; } = true;
public bool IsLeaf { get; set; }
public JsonElement AccessRules { get; set; } = JsonDefaults.Object();
public JsonElement Metadata { get; set; } = JsonDefaults.Object();
public Guid? CreatedBy { get; set; }
public Guid? UpdatedBy { get; set; }
@@ -100,6 +101,7 @@ public sealed class QuestionCollection : AuditableTenantEntity
public QuestionCollectionType CollectionType { get; set; } = QuestionCollectionType.Dynamic;
public QuestionCollectionSourceType SourceType { get; set; } = QuestionCollectionSourceType.Filters;
public JsonElement Filters { get; set; } = JsonDefaults.Object();
public JsonElement AccessRules { get; set; } = JsonDefaults.Object();
public int QuestionCount { get; set; }
public decimal? TotalScore { get; set; }
public int? DurationMinutes { get; set; }
@@ -163,6 +165,7 @@ public sealed class PracticeBlueprint : AuditableTenantEntity
public decimal? PassScore { get; set; }
public JsonElement Sections { get; set; } = JsonDefaults.Array();
public JsonElement Rules { get; set; } = JsonDefaults.Object();
public JsonElement AccessRules { get; set; } = JsonDefaults.Object();
public ContentStatus Status { get; set; } = ContentStatus.Active;
public int SortOrder { get; set; }
public Guid? CreatedBy { get; set; }

View File

@@ -20,9 +20,20 @@ public sealed class PracticeSession : TenantEntity
public int? DurationMinutes { get; set; }
public decimal? TotalScore { get; set; }
public DateTimeOffset? ExpiresAt { get; set; }
public PracticeAccessMode AccessMode { get; set; } = PracticeAccessMode.Free;
public Guid? AccessEntitlementId { get; set; }
public int ConsumedFreeQuota { get; set; }
public JsonElement AccessSnapshot { get; set; } = JsonDefaults.Object();
public JsonElement Metadata { get; set; } = JsonDefaults.Object();
}
public enum PracticeAccessMode
{
Free,
Svip,
Staff
}
public sealed class AnswerRecord : TenantEntity
{
public Guid UserId { get; set; }

View File

@@ -0,0 +1,229 @@
using System.Text.Json;
using Tiku.Domain.Common;
namespace Tiku.Domain.Learning;
public sealed class ExamDate : AuditableTenantEntity
{
public Guid? RegionId { get; set; }
public Guid? SchoolId { get; set; }
public string? LegacyId { get; set; }
public string ExamName { get; set; } = string.Empty;
public DateTimeOffset? ExamAt { get; set; }
public string? ExamType { get; set; }
public string? Description { get; set; }
public int SortOrder { get; set; }
public bool IsActive { get; set; } = true;
public JsonElement Metadata { get; set; } = JsonDefaults.Object();
}
public sealed class Report : AuditableTenantEntity
{
public Guid? QuestionId { get; set; }
public Guid? UserId { get; set; }
public Guid? HandledBy { get; set; }
public string? LegacyId { get; set; }
public ReportType? Type { get; set; }
public string? Title { get; set; }
public string? Category { get; set; }
public string? Description { get; set; }
public ReportStatus Status { get; set; } = ReportStatus.Pending;
public ReportPriority Priority { get; set; } = ReportPriority.Normal;
public DateTimeOffset? HandledAt { get; set; }
public string? Resolution { get; set; }
public string? Contact { get; set; }
public JsonElement Attachments { get; set; } = JsonDefaults.Array();
public JsonElement Metadata { get; set; } = JsonDefaults.Object();
}
public sealed class ReportStatusEvent : Entity
{
public Guid TenantId { get; set; }
public Guid ReportId { get; set; }
public ReportStatus? FromStatus { get; set; }
public ReportStatus ToStatus { get; set; } = ReportStatus.Pending;
public string? Note { get; set; }
public Guid? ActorUserId { get; set; }
public JsonElement Metadata { get; set; } = JsonDefaults.Object();
public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.UtcNow;
}
public sealed class UserScoreEvent : Entity
{
public Guid TenantId { get; set; }
public Guid UserId { get; set; }
public UserScoreEventType EventType { get; set; } = UserScoreEventType.ManualAdjust;
public int Points { get; set; }
public int BalanceAfter { get; set; }
public string? SourceType { get; set; }
public Guid? SourceId { get; set; }
public string? IdempotencyKey { get; set; }
public JsonElement Metadata { get; set; } = JsonDefaults.Object();
public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.UtcNow;
}
public sealed class PracticeDailyUsage : AuditableTenantEntity
{
public Guid UserId { get; set; }
public DateOnly UsageDate { get; set; } = DateOnly.FromDateTime(DateTime.UtcNow);
public PracticeUsageScopeType ScopeType { get; set; } = PracticeUsageScopeType.Tenant;
public Guid? ScopeId { get; set; }
public int FreeLimit { get; set; } = 25;
public int UsedCount { get; set; }
public JsonElement Metadata { get; set; } = JsonDefaults.Object();
}
public sealed class PracticeAccessEvent : Entity
{
public Guid TenantId { get; set; }
public Guid? UserId { get; set; }
public Guid? PracticeSessionId { get; set; }
public PracticeAccessEventType EventType { get; set; } = PracticeAccessEventType.SessionCreated;
public PracticeAccessEventMode AccessMode { get; set; } = PracticeAccessEventMode.Free;
public int RequestedCount { get; set; }
public int GrantedCount { get; set; }
public int ConsumedFreeQuota { get; set; }
public string? Reason { get; set; }
public PracticeUsageScopeType? ScopeType { get; set; }
public Guid? ScopeId { get; set; }
public Guid? EntitlementId { get; set; }
public JsonElement Metadata { get; set; } = JsonDefaults.Object();
public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.UtcNow;
}
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 string Mode { get; set; } = "mock_exam";
public int TotalQuestions { get; set; }
public int AnsweredCount { get; set; }
public int CorrectCount { get; set; }
public int WrongCount { get; set; }
public int UnansweredCount { get; set; }
public decimal Score { get; set; }
public decimal TotalScore { get; set; }
public decimal Accuracy { get; set; }
public int DurationSeconds { get; set; }
public DateTimeOffset? StartedAt { get; set; }
public DateTimeOffset SubmittedAt { get; set; } = DateTimeOffset.UtcNow;
public JsonElement SectionStats { get; set; } = JsonDefaults.Array();
public JsonElement QuestionResults { get; set; } = JsonDefaults.Array();
public JsonElement WrongQuestionIds { get; set; } = JsonDefaults.Array();
public JsonElement Metadata { get; set; } = JsonDefaults.Object();
}
public sealed class PracticeSessionReportSection : AuditableTenantEntity
{
public Guid ReportId { get; set; }
public Guid PracticeSessionId { get; set; }
public string SectionKey { get; set; } = string.Empty;
public string? SectionName { get; set; }
public string? QuestionType { get; set; }
public int QuestionCount { get; set; }
public int AnsweredCount { get; set; }
public int CorrectCount { get; set; }
public int WrongCount { get; set; }
public int UnansweredCount { get; set; }
public decimal Score { get; set; }
public decimal TotalScore { get; set; }
public decimal Accuracy { get; set; }
public int SortOrder { get; set; }
public JsonElement Metadata { get; set; } = JsonDefaults.Object();
}
public sealed class DashboardDailyStat : AuditableTenantEntity
{
public Guid? RegionId { get; set; }
public string? LegacyId { get; set; }
public string? LegacyRegionId { get; set; }
public DateOnly StatDate { get; set; }
public int NewUsers { get; set; }
public int NewQuestions { get; set; }
public int NewOrders { get; set; }
public int NewRevenueCents { get; set; }
public int ActiveUsers { get; set; }
public DateTimeOffset? RebuiltAt { get; set; }
}
public sealed class RevenueDailyStat : AuditableTenantEntity
{
public Guid? RegionId { get; set; }
public string? LegacyId { get; set; }
public string? LegacyRegionId { get; set; }
public DateOnly StatDate { get; set; }
public string? SaleType { get; set; }
public int RealRevenueCents { get; set; }
public int OrderCount { get; set; }
public int CodeCount { get; set; }
public int CodeUsed { get; set; }
public int CodeEstimated { get; set; }
public int EstimatedRevenueCents { get; set; }
public DateTimeOffset? RebuiltAt { get; set; }
}
public enum ReportType
{
QuestionError,
ContentError,
VideoError,
AssetError,
SystemBug,
Suggestion,
Other
}
public enum ReportStatus
{
Pending,
Accepted,
Rejected,
Resolved,
Closed
}
public enum ReportPriority
{
Low,
Normal,
High,
Urgent
}
public enum UserScoreEventType
{
CheckIn,
ManualAdjust,
FeedbackReward,
ActivityReward,
RedeemCost
}
public enum PracticeUsageScopeType
{
Tenant,
Region,
Subject,
QuestionBank,
ContentEntry,
ContentNode,
Collection,
Blueprint
}
public enum PracticeAccessEventType
{
SessionCreated,
SessionDenied,
QuotaConsumed
}
public enum PracticeAccessEventMode
{
Free,
Svip,
Staff,
Denied
}