Files
tiku-backend.net/Tiku.Infrastructure/Growth/TenantAdmin/ReferralAdministrationService.cs
xiong 33375a38d7
Some checks failed
ci / release-gate (push) Has been cancelled
refactor(architecture): harden module boundaries
2026-08-04 12:10:36 +08:00

116 lines
4.9 KiB
C#

using Microsoft.EntityFrameworkCore;
using Tiku.Application.Growth;
using Tiku.Domain.Common;
using Tiku.Domain.Growth;
namespace Tiku.Infrastructure.Growth;
internal sealed class ReferralAdministrationService(
ReferralServiceDependencies dependencies,
IStudentReferralService studentReferralService)
: ReferralServiceBase(dependencies), IReferralAdministrationService
{
public async Task<ReferralBindResult> ManualBindAsync(
ReferralAdminActor actor,
ManualBindReferralCommand command,
CancellationToken cancellationToken = default)
{
await AssertAdminAsync(actor, cancellationToken);
if (command.StudentUserId == command.ReferrerUserId)
throw new ReferralException("User cannot bind to own referral code.", "self_referral_not_allowed");
var refCode = await growthPersistence.ReferralCodes
.Where(item =>
item.TenantId == actor.TenantId &&
item.UserId == command.ReferrerUserId &&
item.Status == ReferralCodeStatus.Active)
.Select(item => item.Code)
.FirstOrDefaultAsync(cancellationToken);
if (refCode is null)
refCode = (await studentReferralService.GetOrCreateInviteCodeAsync(
new ReferralActor(actor.TenantId, command.ReferrerUserId),
new ReferralInviteCommand("manual"),
cancellationToken)).InviteCode;
var before = await growthPersistence.ReferralLeads
.AsNoTracking()
.FirstOrDefaultAsync(item =>
item.TenantId == actor.TenantId &&
item.StudentUserId == command.StudentUserId,
cancellationToken);
var lead = await BindLeadCoreAsync(
actor.TenantId,
command.StudentUserId,
command.ReferrerUserId,
refCode,
NormalizeChoice(command.Source, AllowedSources, "manual", "invalid_referral_source"),
ReferralLeadBindType.Manual,
command.Force,
command.Metadata,
cancellationToken);
lead.AssignedBy = actor.UserId;
lead.AssignedAt = DateTimeOffset.UtcNow;
var crmQueue = before?.ReferrerUserId == lead.ReferrerUserId
? null
: await EnqueueCrmIfEnabledAsync(actor.TenantId, lead, "referral.manual_bind", cancellationToken);
await growthPersistence.SaveChangesAsync(cancellationToken);
return new ReferralBindResult(ToLeadItem(lead, before?.ReferrerUserId != lead.ReferrerUserId),
ToQueuePreview(crmQueue));
}
public async Task<ReferralList<ReferralTeamItem>> GetTeamAsync(
ReferralAdminActor actor,
ReferralTeamQuery query,
CancellationToken cancellationToken = default)
{
await AssertAdminAsync(actor, cancellationToken);
var edges = growthPersistence.ReferralTeamEdges
.AsNoTracking()
.Where(item => item.TenantId == actor.TenantId);
if (query.LeaderUserId.HasValue) edges = edges.Where(item => item.LeaderUserId == query.LeaderUserId.Value);
var items = await edges
.OrderBy(item => item.RelationType)
.ThenBy(item => item.MemberUserId)
.ToArrayAsync(cancellationToken);
return new ReferralList<ReferralTeamItem>(items.Select(ToTeamItem).ToArray());
}
public async Task<ReferralTeamItem> UpsertTeamAsync(
ReferralAdminActor actor,
UpsertReferralTeamCommand command,
CancellationToken cancellationToken = default)
{
await AssertAdminAsync(actor, cancellationToken);
await AssertActiveMemberAsync(actor.TenantId, command.MemberUserId, cancellationToken);
if (command.LeaderUserId.HasValue)
await AssertActiveMemberAsync(actor.TenantId, command.LeaderUserId.Value, cancellationToken);
var relationType = ParseEnum(command.RelationType, ReferralTeamRelationType.SalesTeam,
"invalid_referral_team_relation");
var status = ParseEnum(command.Status, ReferralTeamEdgeStatus.Active, "invalid_referral_team_status");
var edge = await growthPersistence.ReferralTeamEdges
.FirstOrDefaultAsync(item =>
item.TenantId == actor.TenantId &&
item.MemberUserId == command.MemberUserId &&
item.RelationType == relationType,
cancellationToken);
if (edge is null)
{
edge = new ReferralTeamEdge
{
TenantId = actor.TenantId,
MemberUserId = command.MemberUserId,
RelationType = relationType
};
growthPersistence.ReferralTeamEdges.Add(edge);
}
edge.LeaderUserId = command.LeaderUserId;
edge.Status = status;
edge.Metadata = command.Metadata ?? JsonDefaults.Object();
await growthPersistence.SaveChangesAsync(cancellationToken);
return ToTeamItem(edge);
}
}