101 lines
5.1 KiB
C#
101 lines
5.1 KiB
C#
using Microsoft.EntityFrameworkCore;
|
|
using Tiku.Application.TenantAdmin;
|
|
using Tiku.Domain.Operations;
|
|
using Tiku.Domain.Tenancy;
|
|
using Tiku.Infrastructure.Security;
|
|
using CommerceRefundStatus = Tiku.Domain.Commerce.CommerceRefundStatus;
|
|
using OrderStatus = Tiku.Domain.Commerce.OrderStatus;
|
|
using ReconciliationIssueStatus = Tiku.Domain.Commerce.ReconciliationIssueStatus;
|
|
|
|
namespace Tiku.Infrastructure.TenantAdmin;
|
|
|
|
public sealed partial class TenantAdminDirectService
|
|
{
|
|
public async Task<TenantAdminOverviewItem> GetOverviewAsync(
|
|
TenantAdminActor actor,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var scope = await RequireDataScopeAsync(actor, cancellationToken);
|
|
var regionIds = scope.RegionIds.ToArray();
|
|
var classIds = scope.ClassIds.ToArray();
|
|
var now = DateTimeOffset.UtcNow;
|
|
var today = new DateTimeOffset(now.UtcDateTime.Date, TimeSpan.Zero);
|
|
var scopedClasses = dbContext.TenantClasses.AsNoTracking()
|
|
.Where(item => item.TenantId == actor.TenantId)
|
|
.ApplyDataScope(
|
|
scope,
|
|
item => item.CreatedBy == actor.UserId,
|
|
item => classIds.Contains(item.Id) ||
|
|
(item.RegionId.HasValue && regionIds.Contains(item.RegionId.Value)));
|
|
var scopedStudents = dbContext.StudentProfiles.AsNoTracking()
|
|
.Where(item => item.TenantId == actor.TenantId)
|
|
.ApplyDataScope(
|
|
scope,
|
|
item => item.UserId == actor.UserId,
|
|
item => item.RegionId.HasValue && regionIds.Contains(item.RegionId.Value));
|
|
var scopedOrders = dbContext.Orders.AsNoTracking()
|
|
.Where(item => item.TenantId == actor.TenantId)
|
|
.ApplyDataScope(
|
|
scope,
|
|
item => item.UserId == actor.UserId,
|
|
item => item.RegionId.HasValue && regionIds.Contains(item.RegionId.Value));
|
|
|
|
var studentCount = await scopedStudents.CountAsync(cancellationToken);
|
|
var classCount =
|
|
await scopedClasses.CountAsync(item => item.Status == TenantRecordStatus.Active, cancellationToken);
|
|
var staffCount = await dbContext.TenantMemberships.AsNoTracking()
|
|
.CountAsync(item =>
|
|
item.TenantId == actor.TenantId &&
|
|
item.Status == MembershipStatus.Active &&
|
|
item.Role != TenantRole.Student,
|
|
cancellationToken);
|
|
var activePracticeCount = await dbContext.PracticeSessions.AsNoTracking()
|
|
.CountAsync(item =>
|
|
item.TenantId == actor.TenantId &&
|
|
item.FinishedAt == null &&
|
|
(!item.ExpiresAt.HasValue || item.ExpiresAt > now),
|
|
cancellationToken);
|
|
var todayPracticeCount = await dbContext.PracticeSessions.AsNoTracking()
|
|
.CountAsync(item => item.TenantId == actor.TenantId && item.StartedAt >= today, cancellationToken);
|
|
var pendingFollowupCount = await dbContext.TenantStudentFollowups.AsNoTracking()
|
|
.CountAsync(item =>
|
|
item.TenantId == actor.TenantId &&
|
|
(item.Status == StudentFollowupStatus.Open || item.Status == StudentFollowupStatus.InProgress),
|
|
cancellationToken);
|
|
var unreadNotificationCount = await dbContext.UserNotifications.AsNoTracking()
|
|
.CountAsync(item => item.TenantId == actor.TenantId && item.Status == NotificationStatus.Unread,
|
|
cancellationToken);
|
|
var paidOrderCount = await scopedOrders.CountAsync(item => item.Status == OrderStatus.Paid, cancellationToken);
|
|
var revenueCents = await scopedOrders
|
|
.Where(item => item.Status == OrderStatus.Paid || item.Status == OrderStatus.PartiallyRefunded ||
|
|
item.Status == OrderStatus.Refunded)
|
|
.SumAsync(item => item.AmountCents - item.RefundedAmountCents, cancellationToken);
|
|
var pendingRefundCount = await dbContext.CommerceRefundRequests.AsNoTracking()
|
|
.CountAsync(item =>
|
|
item.TenantId == actor.TenantId &&
|
|
(item.Status == CommerceRefundStatus.Requested ||
|
|
item.Status == CommerceRefundStatus.Approved ||
|
|
item.Status == CommerceRefundStatus.Processing),
|
|
cancellationToken);
|
|
var openReconciliationIssueCount = await dbContext.CommerceReconciliationIssues.AsNoTracking()
|
|
.CountAsync(item =>
|
|
item.TenantId == actor.TenantId &&
|
|
item.Status != ReconciliationIssueStatus.Resolved &&
|
|
item.Status != ReconciliationIssueStatus.Ignored,
|
|
cancellationToken);
|
|
|
|
return new TenantAdminOverviewItem(
|
|
studentCount,
|
|
classCount,
|
|
staffCount,
|
|
activePracticeCount,
|
|
todayPracticeCount,
|
|
pendingFollowupCount,
|
|
unreadNotificationCount,
|
|
paidOrderCount,
|
|
revenueCents,
|
|
pendingRefundCount,
|
|
openReconciliationIssueCount,
|
|
now);
|
|
}
|
|
} |