forked from gongxuegit/tiku-backend.net
feat: add referral management endpoints
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Globalization;
|
||||
using System.Text.Json;
|
||||
using Tiku.Application.Growth;
|
||||
|
||||
@@ -101,3 +102,102 @@ public sealed class ReferralQrcodeDto
|
||||
return new ReferralQrcodeCommand(Page, Scene, Provider, QrcodeUrl, Metadata);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ReferralStatsQueryDto
|
||||
{
|
||||
public Guid? ReferrerUserId { get; set; }
|
||||
|
||||
[Range(1, 500)]
|
||||
public int? Limit { get; set; }
|
||||
|
||||
public ReferralStatsQuery ToQuery()
|
||||
{
|
||||
return new ReferralStatsQuery(ReferrerUserId, Limit);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ReferralConversionQueryDto
|
||||
{
|
||||
public Guid? ReferrerUserId { get; set; }
|
||||
|
||||
[RegularExpression("^\\d{4}-\\d{2}-\\d{2}$")]
|
||||
public string? StartDate { get; set; }
|
||||
|
||||
[RegularExpression("^\\d{4}-\\d{2}-\\d{2}$")]
|
||||
public string? EndDate { get; set; }
|
||||
|
||||
[Range(1, 365)]
|
||||
public int? Days { get; set; }
|
||||
|
||||
[Range(1, 100)]
|
||||
public int? Limit { get; set; }
|
||||
|
||||
public ReferralConversionQuery ToQuery()
|
||||
{
|
||||
return new ReferralConversionQuery(
|
||||
ReferrerUserId,
|
||||
ParseDate(StartDate),
|
||||
ParseDate(EndDate),
|
||||
Days,
|
||||
Limit);
|
||||
}
|
||||
|
||||
private static DateOnly? ParseDate(string? value)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(value)
|
||||
? null
|
||||
: DateOnly.ParseExact(value, "yyyy-MM-dd", CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ManualBindReferralDto
|
||||
{
|
||||
[Required]
|
||||
public Guid StudentUserId { get; set; }
|
||||
|
||||
[Required]
|
||||
public Guid ReferrerUserId { get; set; }
|
||||
|
||||
[StringLength(100)]
|
||||
public string? Source { get; set; }
|
||||
|
||||
public bool Force { get; set; }
|
||||
|
||||
public JsonElement? Metadata { get; set; }
|
||||
|
||||
public ManualBindReferralCommand ToCommand()
|
||||
{
|
||||
return new ManualBindReferralCommand(StudentUserId, ReferrerUserId, Source, Force, Metadata);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ReferralTeamQueryDto
|
||||
{
|
||||
public Guid? LeaderUserId { get; set; }
|
||||
|
||||
public ReferralTeamQuery ToQuery()
|
||||
{
|
||||
return new ReferralTeamQuery(LeaderUserId);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class UpsertReferralTeamDto
|
||||
{
|
||||
[Required]
|
||||
public Guid MemberUserId { get; set; }
|
||||
|
||||
public Guid? LeaderUserId { get; set; }
|
||||
|
||||
[StringLength(50)]
|
||||
public string? RelationType { get; set; }
|
||||
|
||||
[StringLength(50)]
|
||||
public string? Status { get; set; }
|
||||
|
||||
public JsonElement? Metadata { get; set; }
|
||||
|
||||
public UpsertReferralTeamCommand ToCommand()
|
||||
{
|
||||
return new UpsertReferralTeamCommand(MemberUserId, LeaderUserId, RelationType, Status, Metadata);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,6 +92,104 @@ public sealed class ReferralController(
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("stats")]
|
||||
[Authorize(Policy = TikuPolicies.TenantAdmin)]
|
||||
[EndpointSummary("查询推荐人个人统计")]
|
||||
[ProducesResponseType<ReferralStatsItem>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ReferralStatsItem>> Stats(
|
||||
[FromQuery] ReferralStatsQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await referralService.GetStatsAsync(
|
||||
ResolveAdminActor(),
|
||||
query.ToQuery(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("sales-stats")]
|
||||
[Authorize(Policy = TikuPolicies.TenantAdmin)]
|
||||
[EndpointSummary("查询销售推荐统计排行")]
|
||||
[ProducesResponseType<ReferralList<ReferralStatsItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ReferralList<ReferralStatsItem>>> SalesStats(
|
||||
[FromQuery] ReferralStatsQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await referralService.GetSalesStatsAsync(
|
||||
ResolveAdminActor(),
|
||||
query.ToQuery(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("conversion-report")]
|
||||
[Authorize(Policy = TikuPolicies.TenantAdmin)]
|
||||
[EndpointSummary("查询推荐转化报告")]
|
||||
[ProducesResponseType<ReferralConversionReport>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ReferralConversionReport>> ConversionReport(
|
||||
[FromQuery] ReferralConversionQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await referralService.GetConversionReportAsync(
|
||||
ResolveAdminActor(),
|
||||
query.ToQuery(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("sales-clients")]
|
||||
[Authorize(Policy = TikuPolicies.TenantAdmin)]
|
||||
[EndpointSummary("查询推荐人名下客户")]
|
||||
[ProducesResponseType<ReferralList<ReferralLeadItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ReferralList<ReferralLeadItem>>> SalesClients(
|
||||
[FromQuery] ReferralStatsQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await referralService.GetClientsAsync(
|
||||
ResolveAdminActor(),
|
||||
query.ToQuery(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("manual-bind")]
|
||||
[Authorize(Policy = TikuPolicies.TenantAdmin)]
|
||||
[EndpointSummary("人工调整学生推荐归属")]
|
||||
[ProducesResponseType<ReferralBindResult>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ReferralBindResult>> ManualBind(
|
||||
ManualBindReferralDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await referralService.ManualBindAsync(
|
||||
ResolveAdminActor(),
|
||||
request.ToCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("team")]
|
||||
[Authorize(Policy = TikuPolicies.TenantAdmin)]
|
||||
[EndpointSummary("查询推荐团队成员")]
|
||||
[ProducesResponseType<ReferralList<ReferralTeamItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ReferralList<ReferralTeamItem>>> Team(
|
||||
[FromQuery] ReferralTeamQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await referralService.GetTeamAsync(
|
||||
ResolveAdminActor(),
|
||||
query.ToQuery(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPut("team")]
|
||||
[Authorize(Policy = TikuPolicies.TenantAdmin)]
|
||||
[EndpointSummary("新增或更新推荐团队关系")]
|
||||
[ProducesResponseType<ReferralTeamItem>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ReferralTeamItem>> UpsertTeam(
|
||||
UpsertReferralTeamDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await referralService.UpsertTeamAsync(
|
||||
ResolveAdminActor(),
|
||||
request.ToCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
private ReferralActor ResolveUserActor()
|
||||
{
|
||||
if (currentTenant.TenantId is null || currentUser.UserId is null)
|
||||
@@ -102,6 +200,16 @@ public sealed class ReferralController(
|
||||
return new ReferralActor(currentTenant.TenantId.Value, currentUser.UserId.Value);
|
||||
}
|
||||
|
||||
private ReferralAdminActor ResolveAdminActor()
|
||||
{
|
||||
if (currentTenant.TenantId is null || currentUser.UserId is null)
|
||||
{
|
||||
throw new ReferralException("Referral admin actor was not resolved.", "referral_access_denied");
|
||||
}
|
||||
|
||||
return new ReferralAdminActor(currentTenant.TenantId.Value, currentUser.UserId.Value);
|
||||
}
|
||||
|
||||
private async Task<Guid> ResolveTenantIdAsync(string? tenantCode, CancellationToken cancellationToken)
|
||||
{
|
||||
if (currentTenant.TenantId.HasValue)
|
||||
|
||||
Reference in New Issue
Block a user