forked from gongxuegit/tiku-backend.net
feat: complete phase six backoffice operations
This commit is contained in:
263
Tiku.Api/Contracts/PlatformAdminDtos.cs
Normal file
263
Tiku.Api/Contracts/PlatformAdminDtos.cs
Normal file
@@ -0,0 +1,263 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.Json;
|
||||
using Tiku.Application.PlatformAdmin;
|
||||
using Tiku.Domain.Common;
|
||||
using Tiku.Domain.Commerce;
|
||||
using Tiku.Domain.Identity;
|
||||
using Tiku.Domain.Platform;
|
||||
using Tiku.Domain.Tenancy;
|
||||
|
||||
namespace Tiku.Api.Contracts;
|
||||
|
||||
public sealed class PlatformAdminQueryDto
|
||||
{
|
||||
[StringLength(32)]
|
||||
public string? Status { get; set; }
|
||||
|
||||
[StringLength(200)]
|
||||
public string? Search { get; set; }
|
||||
|
||||
[Range(1, 200)]
|
||||
public int? Limit { get; set; }
|
||||
|
||||
public PlatformAdminQuery ToQuery() => new(Status, Search, Limit);
|
||||
}
|
||||
|
||||
public sealed class CreatePlatformTenantDto
|
||||
{
|
||||
[Required]
|
||||
[StringLength(100)]
|
||||
public string Slug { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
[StringLength(200)]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
[StringLength(300)]
|
||||
public string? LegalName { get; set; }
|
||||
|
||||
public TenantStatus Status { get; set; } = TenantStatus.Active;
|
||||
public BillingStatus BillingStatus { get; set; } = BillingStatus.Trial;
|
||||
public JsonElement Metadata { get; set; } = JsonDefaults.Object();
|
||||
|
||||
public CreatePlatformTenantCommand ToCommand() => new(Slug, Name, LegalName, Status, BillingStatus, Metadata);
|
||||
}
|
||||
|
||||
public sealed class UpdatePlatformTenantStatusDto
|
||||
{
|
||||
[Required]
|
||||
public Guid TenantId { get; set; }
|
||||
|
||||
public TenantStatus Status { get; set; } = TenantStatus.Active;
|
||||
public BillingStatus BillingStatus { get; set; } = BillingStatus.Active;
|
||||
|
||||
[StringLength(1000)]
|
||||
public string? Reason { get; set; }
|
||||
|
||||
public UpdatePlatformTenantStatusCommand ToCommand() => new(TenantId, Status, BillingStatus, Reason);
|
||||
}
|
||||
|
||||
public sealed class UpsertPlatformTenantBillingProfileDto
|
||||
{
|
||||
[Required]
|
||||
public Guid TenantId { get; set; }
|
||||
|
||||
[StringLength(300)]
|
||||
public string? BillingName { get; set; }
|
||||
|
||||
[StringLength(100)]
|
||||
public string? TaxId { get; set; }
|
||||
|
||||
[StringLength(100)]
|
||||
public string? ContactName { get; set; }
|
||||
|
||||
[StringLength(32)]
|
||||
public string? ContactPhone { get; set; }
|
||||
|
||||
[StringLength(320)]
|
||||
public string? ContactEmail { get; set; }
|
||||
|
||||
[StringLength(1000)]
|
||||
public string? BillingAddress { get; set; }
|
||||
|
||||
[StringLength(300)]
|
||||
public string? InvoiceTitle { get; set; }
|
||||
|
||||
public TenantInvoiceTitleType? InvoiceType { get; set; }
|
||||
|
||||
[StringLength(300)]
|
||||
public string? BankName { get; set; }
|
||||
|
||||
[StringLength(100)]
|
||||
public string? BankAccountMasked { get; set; }
|
||||
|
||||
public JsonElement Metadata { get; set; } = JsonDefaults.Object();
|
||||
|
||||
public UpsertPlatformTenantBillingProfileCommand ToCommand() => new(
|
||||
TenantId,
|
||||
BillingName,
|
||||
TaxId,
|
||||
ContactName,
|
||||
ContactPhone,
|
||||
ContactEmail,
|
||||
BillingAddress,
|
||||
InvoiceTitle,
|
||||
InvoiceType,
|
||||
BankName,
|
||||
BankAccountMasked,
|
||||
Metadata);
|
||||
}
|
||||
|
||||
public sealed class UpsertPlatformSubscriptionDto
|
||||
{
|
||||
[Required]
|
||||
public Guid TenantId { get; set; }
|
||||
|
||||
[Required]
|
||||
[StringLength(100)]
|
||||
public string PlanCode { get; set; } = string.Empty;
|
||||
|
||||
public TenantSubscriptionStatus Status { get; set; } = TenantSubscriptionStatus.Active;
|
||||
public DateTimeOffset? StartsAt { get; set; }
|
||||
public DateTimeOffset? ExpiresAt { get; set; }
|
||||
|
||||
[StringLength(32)]
|
||||
public string? BillingCycle { get; set; }
|
||||
|
||||
[Range(0, int.MaxValue)]
|
||||
public int AmountCents { get; set; }
|
||||
|
||||
public JsonElement Metadata { get; set; } = JsonDefaults.Object();
|
||||
|
||||
public UpsertPlatformSubscriptionCommand ToCommand() => new(
|
||||
TenantId,
|
||||
PlanCode,
|
||||
Status,
|
||||
StartsAt,
|
||||
ExpiresAt,
|
||||
BillingCycle,
|
||||
AmountCents,
|
||||
Metadata);
|
||||
}
|
||||
|
||||
public sealed class UpsertPlatformStaffDto
|
||||
{
|
||||
public Guid? UserId { get; set; }
|
||||
|
||||
[StringLength(320)]
|
||||
public string? Email { get; set; }
|
||||
|
||||
[StringLength(32)]
|
||||
public string? Phone { get; set; }
|
||||
|
||||
[StringLength(200)]
|
||||
public string? Name { get; set; }
|
||||
|
||||
public UserStatus Status { get; set; } = UserStatus.Active;
|
||||
public IReadOnlyCollection<Guid> RoleIds { get; set; } = [];
|
||||
|
||||
public UpsertPlatformStaffCommand ToCommand() => new(UserId, Email, Phone, Name, Status, RoleIds);
|
||||
}
|
||||
|
||||
public sealed class UpdatePlatformStaffStatusDto
|
||||
{
|
||||
[Required]
|
||||
public Guid UserId { get; set; }
|
||||
|
||||
public UserStatus Status { get; set; } = UserStatus.Active;
|
||||
|
||||
[StringLength(1000)]
|
||||
public string? Reason { get; set; }
|
||||
|
||||
public UpdatePlatformStaffStatusCommand ToCommand() => new(UserId, Status, Reason);
|
||||
}
|
||||
|
||||
public sealed class UpdatePlatformAuditAlertStatusDto
|
||||
{
|
||||
[Required]
|
||||
public Guid AlertId { get; set; }
|
||||
|
||||
public PlatformAuditAlertStatus Status { get; set; } = PlatformAuditAlertStatus.Acknowledged;
|
||||
|
||||
[StringLength(1000)]
|
||||
public string? ResolutionNote { get; set; }
|
||||
|
||||
public UpdatePlatformAuditAlertStatusCommand ToCommand() => new(AlertId, Status, ResolutionNote);
|
||||
}
|
||||
|
||||
public sealed class UpsertPlatformDunningChannelDto
|
||||
{
|
||||
public Guid? ChannelId { get; set; }
|
||||
|
||||
[Required]
|
||||
[StringLength(100)]
|
||||
public string ChannelCode { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
[StringLength(200)]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
[StringLength(1000)]
|
||||
public string? Description { get; set; }
|
||||
|
||||
public bool Enabled { get; set; } = true;
|
||||
public PlatformDunningProvider Provider { get; set; } = PlatformDunningProvider.Generic;
|
||||
|
||||
[Required]
|
||||
[StringLength(2048)]
|
||||
public string WebhookUrl { get; set; } = string.Empty;
|
||||
|
||||
[StringLength(300)]
|
||||
public string? SecretRef { get; set; }
|
||||
|
||||
public IReadOnlyCollection<string> ReminderTypes { get; set; } = ["overdue", "final_notice"];
|
||||
public IReadOnlyCollection<string> ReminderChannels { get; set; } = ["internal"];
|
||||
|
||||
[Range(1, 20)]
|
||||
public int MinReminderLevel { get; set; } = 1;
|
||||
|
||||
public IReadOnlyCollection<Guid> TenantIds { get; set; } = [];
|
||||
|
||||
[Range(1, 60)]
|
||||
public int TimeoutSeconds { get; set; } = 10;
|
||||
|
||||
public JsonElement Metadata { get; set; } = JsonDefaults.Object();
|
||||
|
||||
public UpsertPlatformDunningChannelCommand ToCommand() => new(
|
||||
ChannelId,
|
||||
ChannelCode,
|
||||
Name,
|
||||
Description,
|
||||
Enabled,
|
||||
Provider,
|
||||
WebhookUrl,
|
||||
SecretRef,
|
||||
ReminderTypes,
|
||||
ReminderChannels,
|
||||
MinReminderLevel,
|
||||
TenantIds,
|
||||
TimeoutSeconds,
|
||||
Metadata);
|
||||
}
|
||||
|
||||
public sealed class DisablePlatformDunningChannelDto
|
||||
{
|
||||
[Required]
|
||||
public Guid ChannelId { get; set; }
|
||||
|
||||
[StringLength(1000)]
|
||||
public string? Reason { get; set; }
|
||||
|
||||
public DisablePlatformDunningChannelCommand ToCommand() => new(ChannelId, Reason);
|
||||
}
|
||||
|
||||
public sealed class RetryPlatformDunningEventDto
|
||||
{
|
||||
[Required]
|
||||
public Guid EventId { get; set; }
|
||||
|
||||
[StringLength(1000)]
|
||||
public string? Reason { get; set; }
|
||||
|
||||
public RetryPlatformDunningEventCommand ToCommand() => new(EventId, Reason);
|
||||
}
|
||||
@@ -252,6 +252,113 @@ public sealed class UpdateTenantAdminStudentStatusDto
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class TenantAdminStudentImportRowDto
|
||||
{
|
||||
public TenantAdminUserLookupDto User { get; set; } = new();
|
||||
public Guid? RegionId { get; set; }
|
||||
public Guid? ClassId { get; set; }
|
||||
public string? AvatarPreset { get; set; }
|
||||
public JsonElement RawProfile { get; set; } = JsonDefaults.Object();
|
||||
|
||||
public TenantAdminStudentImportRowCommand ToCommand()
|
||||
{
|
||||
return new TenantAdminStudentImportRowCommand(User.ToCommand(), RegionId, ClassId, AvatarPreset, RawProfile);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class TenantAdminStudentImportDto
|
||||
{
|
||||
[Required]
|
||||
public IReadOnlyCollection<TenantAdminStudentImportRowDto> Rows { get; set; } = [];
|
||||
|
||||
public TenantAdminStudentImportCommand ToCommand()
|
||||
{
|
||||
return new TenantAdminStudentImportCommand(Rows.Select(row => row.ToCommand()).ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class TenantAdminBulkAssignClassDto
|
||||
{
|
||||
[Required]
|
||||
public Guid ClassId { get; set; }
|
||||
|
||||
[Required]
|
||||
public IReadOnlyCollection<Guid> UserIds { get; set; } = [];
|
||||
|
||||
public TenantAdminBulkAssignClassCommand ToCommand()
|
||||
{
|
||||
return new TenantAdminBulkAssignClassCommand(ClassId, UserIds);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class TenantAdminBulkStatusDto
|
||||
{
|
||||
[Required]
|
||||
public IReadOnlyCollection<Guid> UserIds { get; set; } = [];
|
||||
|
||||
public string? Status { get; set; }
|
||||
|
||||
[StringLength(1000)]
|
||||
public string? Reason { get; set; }
|
||||
|
||||
public TenantAdminBulkStatusCommand ToCommand()
|
||||
{
|
||||
return new TenantAdminBulkStatusCommand(UserIds, Status, Reason);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class UpsertTenantSupervisionRuleDto
|
||||
{
|
||||
[Required]
|
||||
[StringLength(100)]
|
||||
public string Code { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
[StringLength(200)]
|
||||
public string Title { get; set; } = string.Empty;
|
||||
|
||||
public bool Enabled { get; set; } = true;
|
||||
|
||||
[Range(0, 3650)]
|
||||
public int? DaysWithoutCheckIn { get; set; }
|
||||
|
||||
[Range(0, int.MaxValue)]
|
||||
public int? MaxQuestionsAnsweredToday { get; set; }
|
||||
|
||||
[StringLength(50)]
|
||||
public string? FollowupType { get; set; }
|
||||
|
||||
[StringLength(50)]
|
||||
public string? Priority { get; set; }
|
||||
|
||||
public JsonElement Metadata { get; set; } = JsonDefaults.Object();
|
||||
|
||||
public UpsertTenantSupervisionRuleCommand ToCommand()
|
||||
{
|
||||
return new UpsertTenantSupervisionRuleCommand(
|
||||
Code,
|
||||
Title,
|
||||
Enabled,
|
||||
DaysWithoutCheckIn,
|
||||
MaxQuestionsAnsweredToday,
|
||||
FollowupType,
|
||||
Priority,
|
||||
Metadata);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class TenantSupervisionGenerateDto
|
||||
{
|
||||
public IReadOnlyCollection<Guid>? UserIds { get; set; }
|
||||
public Guid? AssignedToUserId { get; set; }
|
||||
public DateTimeOffset? DueAt { get; set; }
|
||||
|
||||
public TenantSupervisionGenerateCommand ToCommand()
|
||||
{
|
||||
return new TenantSupervisionGenerateCommand(UserIds, AssignedToUserId, DueAt);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class UpsertTenantAdminStudentNoteDto
|
||||
{
|
||||
public Guid? Id { get; set; }
|
||||
|
||||
@@ -334,6 +334,111 @@ public sealed class UpdateReconciliationIssueDto
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class PreviewReconciliationImportDto
|
||||
{
|
||||
[Required]
|
||||
[StringLength(50)]
|
||||
public string Provider { get; set; } = string.Empty;
|
||||
|
||||
public DateOnly BillDate { get; set; }
|
||||
public ReconciliationBillType BillType { get; set; } = ReconciliationBillType.Combined;
|
||||
|
||||
[Required]
|
||||
[StringLength(300)]
|
||||
public string SourceName { get; set; } = string.Empty;
|
||||
|
||||
public JsonElement Rows { get; set; } = JsonDefaults.Array();
|
||||
|
||||
public PreviewReconciliationImportCommand ToPreviewCommand() => new(Provider, BillDate, BillType, SourceName, Rows);
|
||||
|
||||
public ImportReconciliationCommand ToImportCommand() => new(Provider, BillDate, BillType, SourceName, Rows);
|
||||
}
|
||||
|
||||
public sealed class CreateAdjustmentVoucherDto
|
||||
{
|
||||
public Guid? IssueId { get; set; }
|
||||
public Guid? BatchId { get; set; }
|
||||
public Guid? ItemId { get; set; }
|
||||
public Guid? OrderId { get; set; }
|
||||
public Guid? PaymentId { get; set; }
|
||||
public Guid? RefundRequestId { get; set; }
|
||||
public CommerceAdjustmentDirection Direction { get; set; } = CommerceAdjustmentDirection.IncreaseRevenue;
|
||||
|
||||
[Range(1, int.MaxValue)]
|
||||
public int AmountCents { get; set; }
|
||||
|
||||
[StringLength(10)]
|
||||
public string? Currency { get; set; }
|
||||
|
||||
[Required]
|
||||
[StringLength(1000)]
|
||||
public string Reason { get; set; } = string.Empty;
|
||||
|
||||
[StringLength(500)]
|
||||
public string? ProofAssetKey { get; set; }
|
||||
|
||||
public JsonElement Metadata { get; set; } = JsonDefaults.Object();
|
||||
|
||||
public CreateAdjustmentVoucherCommand ToCommand() => new(
|
||||
IssueId,
|
||||
BatchId,
|
||||
ItemId,
|
||||
OrderId,
|
||||
PaymentId,
|
||||
RefundRequestId,
|
||||
Direction,
|
||||
AmountCents,
|
||||
Currency,
|
||||
Reason,
|
||||
ProofAssetKey,
|
||||
Metadata);
|
||||
}
|
||||
|
||||
public sealed class UpdateAdjustmentVoucherStatusDto
|
||||
{
|
||||
[Required]
|
||||
public Guid VoucherId { get; set; }
|
||||
|
||||
public CommerceAdjustmentVoucherStatus Status { get; set; } = CommerceAdjustmentVoucherStatus.PendingReview;
|
||||
|
||||
[StringLength(1000)]
|
||||
public string? Note { get; set; }
|
||||
|
||||
public UpdateAdjustmentVoucherStatusCommand ToCommand() => new(VoucherId, Status, Note);
|
||||
}
|
||||
|
||||
public sealed class RequestProviderBillJobDto
|
||||
{
|
||||
[Required]
|
||||
[StringLength(50)]
|
||||
public string Provider { get; set; } = string.Empty;
|
||||
|
||||
public DateOnly BillDate { get; set; }
|
||||
public ReconciliationBillType BillType { get; set; } = ReconciliationBillType.Combined;
|
||||
public DateTimeOffset? RunAfter { get; set; }
|
||||
|
||||
public RequestProviderBillJobCommand ToCommand() => new(Provider, BillDate, BillType, RunAfter);
|
||||
}
|
||||
|
||||
public sealed class RefundNotificationDto
|
||||
{
|
||||
[Required]
|
||||
[StringLength(100)]
|
||||
public string RefundNo { get; set; } = string.Empty;
|
||||
|
||||
[StringLength(200)]
|
||||
public string? ProviderRefundNo { get; set; }
|
||||
|
||||
public CommerceRefundStatus Status { get; set; } = CommerceRefundStatus.Succeeded;
|
||||
|
||||
[StringLength(200)]
|
||||
public string? EventId { get; set; }
|
||||
|
||||
public JsonElement Payload { get; set; } = JsonDefaults.Object();
|
||||
|
||||
public RefundNotificationCommand ToCommand(string provider) => new(provider, RefundNo, ProviderRefundNo, Status, EventId, Payload);
|
||||
}
|
||||
|
||||
public sealed class UpdatePointExchangeOrderStatusDto
|
||||
{
|
||||
[Required]
|
||||
|
||||
@@ -7,6 +7,7 @@ using Tiku.Api.Contracts;
|
||||
using Tiku.Application.Commerce;
|
||||
using Tiku.Application.Security;
|
||||
using Tiku.Application.Tenancy;
|
||||
using Tiku.Domain.Commerce;
|
||||
|
||||
namespace Tiku.Api.Controllers;
|
||||
|
||||
@@ -16,6 +17,7 @@ namespace Tiku.Api.Controllers;
|
||||
[Route("api/commerce")]
|
||||
public sealed class CommerceController(
|
||||
ICommerceService commerceService,
|
||||
ICommerceAdminService commerceAdminService,
|
||||
ICurrentUser currentUser,
|
||||
ITenantContext currentTenant,
|
||||
ITenantContextInitializer tenantInitializer,
|
||||
@@ -151,6 +153,36 @@ public sealed class CommerceController(
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[HttpPost("refunds/notify/wechat_pay")]
|
||||
[EndpointSummary("微信退款结果回调")]
|
||||
[ProducesResponseType<CommerceRefundRequest>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<CommerceRefundRequest>> WechatRefundNotify(
|
||||
[FromQuery] string? tenantCode,
|
||||
RefundNotificationDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await commerceAdminService.ProcessRefundNotificationAsync(
|
||||
await ResolveNotificationTenantAsync(tenantCode, cancellationToken),
|
||||
request.ToCommand(PaymentProviders.WechatPay),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[HttpPost("refunds/notify/alipay")]
|
||||
[EndpointSummary("支付宝退款结果回调")]
|
||||
[ProducesResponseType<CommerceRefundRequest>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<CommerceRefundRequest>> AlipayRefundNotify(
|
||||
[FromQuery] string? tenantCode,
|
||||
RefundNotificationDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await commerceAdminService.ProcessRefundNotificationAsync(
|
||||
await ResolveNotificationTenantAsync(tenantCode, cancellationToken),
|
||||
request.ToCommand(PaymentProviders.Alipay),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
private async Task<Guid> ResolveNotificationTenantAsync(
|
||||
string? tenantCode,
|
||||
CancellationToken cancellationToken)
|
||||
|
||||
266
Tiku.Api/Controllers/PlatformAdminController.cs
Normal file
266
Tiku.Api/Controllers/PlatformAdminController.cs
Normal file
@@ -0,0 +1,266 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Tiku.Api.Contracts;
|
||||
using Tiku.Application.PlatformAdmin;
|
||||
using Tiku.Application.Security;
|
||||
using Tiku.Domain.Platform;
|
||||
|
||||
namespace Tiku.Api.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Authorize(Policy = BackendPermissions.PlatformDashboardView)]
|
||||
[Produces("application/json")]
|
||||
[Route("api/platform-admin")]
|
||||
public sealed class PlatformAdminController(
|
||||
IPlatformAdminService platformAdminService,
|
||||
ICurrentUser currentUser) : ControllerBase
|
||||
{
|
||||
[HttpGet("overview")]
|
||||
[EndpointSummary("查询平台经营概览")]
|
||||
[ProducesResponseType<PlatformOverview>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PlatformOverview>> Overview(CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await platformAdminService.GetOverviewAsync(ResolveActor(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("tenants")]
|
||||
[Authorize(Policy = BackendPermissions.PlatformTenantManage)]
|
||||
[EndpointSummary("查询平台租户列表")]
|
||||
[ProducesResponseType<PlatformTenantList>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PlatformTenantList>> Tenants(
|
||||
[FromQuery] PlatformAdminQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await platformAdminService.GetTenantsAsync(ResolveActor(), query.ToQuery(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("tenants")]
|
||||
[Authorize(Policy = BackendPermissions.PlatformTenantManage)]
|
||||
[EndpointSummary("创建平台租户")]
|
||||
[ProducesResponseType<PlatformTenantItem>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PlatformTenantItem>> CreateTenant(
|
||||
CreatePlatformTenantDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await platformAdminService.CreateTenantAsync(ResolveActor(), request.ToCommand(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("tenants/detail")]
|
||||
[Authorize(Policy = BackendPermissions.PlatformTenantManage)]
|
||||
[EndpointSummary("查询平台租户详情")]
|
||||
[ProducesResponseType<PlatformTenantDetail>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PlatformTenantDetail>> TenantDetail(
|
||||
[FromQuery] Guid tenantId,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await platformAdminService.GetTenantDetailAsync(ResolveActor(), tenantId, cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPatch("tenants/status")]
|
||||
[Authorize(Policy = BackendPermissions.PlatformTenantManage)]
|
||||
[EndpointSummary("更新租户业务与账务状态")]
|
||||
[ProducesResponseType<PlatformTenantItem>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PlatformTenantItem>> TenantStatus(
|
||||
UpdatePlatformTenantStatusDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await platformAdminService.UpdateTenantStatusAsync(ResolveActor(), request.ToCommand(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPut("tenants/billing-profile")]
|
||||
[Authorize(Policy = BackendPermissions.PlatformTenantManage)]
|
||||
[EndpointSummary("保存租户账务与开票资料")]
|
||||
[ProducesResponseType<TenantBillingProfileItem>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantBillingProfileItem>> BillingProfile(
|
||||
UpsertPlatformTenantBillingProfileDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await platformAdminService.UpsertTenantBillingProfileAsync(ResolveActor(), request.ToCommand(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("plans")]
|
||||
[Authorize(Policy = BackendPermissions.PlatformTenantManage)]
|
||||
[EndpointSummary("查询平台 SaaS 套餐")]
|
||||
[ProducesResponseType<PlatformPlanList>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PlatformPlanList>> Plans(
|
||||
[FromQuery] PlatformAdminQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await platformAdminService.GetPlansAsync(ResolveActor(), query.ToQuery(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("subscriptions")]
|
||||
[Authorize(Policy = BackendPermissions.PlatformTenantManage)]
|
||||
[EndpointSummary("创建或调整租户订阅")]
|
||||
[ProducesResponseType<PlatformTenantSubscriptionItem>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PlatformTenantSubscriptionItem>> UpsertSubscription(
|
||||
UpsertPlatformSubscriptionDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await platformAdminService.UpsertSubscriptionAsync(ResolveActor(), request.ToCommand(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("domains")]
|
||||
[Authorize(Policy = BackendPermissions.PlatformTenantManage)]
|
||||
[EndpointSummary("查询租户域名状态")]
|
||||
[ProducesResponseType<PlatformDomainList>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PlatformDomainList>> Domains(
|
||||
[FromQuery] PlatformAdminQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await platformAdminService.GetDomainsAsync(ResolveActor(), query.ToQuery(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("domains/{domainId:guid}/recheck")]
|
||||
[Authorize(Policy = BackendPermissions.PlatformTenantManage)]
|
||||
[EndpointSummary("重新触发租户域名 DNS/TLS 验证")]
|
||||
[ProducesResponseType<PlatformDomainRecheckResult>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PlatformDomainRecheckResult>> RecheckDomain(
|
||||
Guid domainId,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await platformAdminService.RecheckDomainAsync(ResolveActor(), domainId, cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("staff")]
|
||||
[Authorize(Policy = BackendPermissions.PlatformStaffManage)]
|
||||
[EndpointSummary("查询平台员工列表")]
|
||||
[ProducesResponseType<PlatformStaffList>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PlatformStaffList>> Staff(
|
||||
[FromQuery] PlatformAdminQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await platformAdminService.GetStaffAsync(ResolveActor(), query.ToQuery(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPut("staff")]
|
||||
[Authorize(Policy = BackendPermissions.PlatformStaffManage)]
|
||||
[EndpointSummary("创建或更新平台员工")]
|
||||
[ProducesResponseType<PlatformStaffItem>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PlatformStaffItem>> UpsertStaff(
|
||||
UpsertPlatformStaffDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await platformAdminService.UpsertStaffAsync(ResolveActor(), request.ToCommand(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPatch("staff/status")]
|
||||
[Authorize(Policy = BackendPermissions.PlatformStaffManage)]
|
||||
[EndpointSummary("启用或禁用平台员工")]
|
||||
[ProducesResponseType<PlatformStaffItem>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PlatformStaffItem>> StaffStatus(
|
||||
UpdatePlatformStaffStatusDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await platformAdminService.UpdateStaffStatusAsync(ResolveActor(), request.ToCommand(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("audit-logs")]
|
||||
[Authorize(Policy = BackendPermissions.PlatformAuditView)]
|
||||
[EndpointSummary("查询平台审计日志")]
|
||||
[ProducesResponseType<PlatformAuditLogList>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PlatformAuditLogList>> AuditLogs(
|
||||
[FromQuery] PlatformAdminQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await platformAdminService.GetAuditLogsAsync(ResolveActor(), query.ToQuery(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("audit-alerts")]
|
||||
[Authorize(Policy = BackendPermissions.PlatformAuditView)]
|
||||
[EndpointSummary("查询平台审计告警")]
|
||||
[ProducesResponseType<PlatformAuditAlertList>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PlatformAuditAlertList>> AuditAlerts(
|
||||
[FromQuery] PlatformAdminQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await platformAdminService.GetAuditAlertsAsync(ResolveActor(), query.ToQuery(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("audit-alerts/status")]
|
||||
[Authorize(Policy = BackendPermissions.PlatformAuditView)]
|
||||
[EndpointSummary("更新平台审计告警状态")]
|
||||
[ProducesResponseType<PlatformAuditAlert>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PlatformAuditAlert>> AuditAlertStatus(
|
||||
UpdatePlatformAuditAlertStatusDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await platformAdminService.UpdateAuditAlertStatusAsync(ResolveActor(), request.ToCommand(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("dunning-notification-channels")]
|
||||
[Authorize(Policy = BackendPermissions.PlatformBillingNotification)]
|
||||
[EndpointSummary("查询平台催缴通知渠道")]
|
||||
[ProducesResponseType<PlatformDunningChannelList>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PlatformDunningChannelList>> DunningChannels(
|
||||
[FromQuery] PlatformAdminQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await platformAdminService.GetDunningChannelsAsync(ResolveActor(), query.ToQuery(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPut("dunning-notification-channels")]
|
||||
[Authorize(Policy = BackendPermissions.PlatformBillingNotification)]
|
||||
[EndpointSummary("创建或更新平台催缴通知渠道")]
|
||||
[ProducesResponseType<PlatformDunningChannelItem>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PlatformDunningChannelItem>> UpsertDunningChannel(
|
||||
UpsertPlatformDunningChannelDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await platformAdminService.UpsertDunningChannelAsync(ResolveActor(), request.ToCommand(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("dunning-notification-channels/disable")]
|
||||
[Authorize(Policy = BackendPermissions.PlatformBillingNotification)]
|
||||
[EndpointSummary("禁用平台催缴通知渠道")]
|
||||
[ProducesResponseType<PlatformDunningChannelItem>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PlatformDunningChannelItem>> DisableDunningChannel(
|
||||
DisablePlatformDunningChannelDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await platformAdminService.DisableDunningChannelAsync(ResolveActor(), request.ToCommand(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("dunning-notification-events")]
|
||||
[Authorize(Policy = BackendPermissions.PlatformBillingNotification)]
|
||||
[EndpointSummary("查询平台催缴通知事件")]
|
||||
[ProducesResponseType<PlatformDunningEventList>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PlatformDunningEventList>> DunningEvents(
|
||||
[FromQuery] PlatformAdminQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await platformAdminService.GetDunningEventsAsync(ResolveActor(), query.ToQuery(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("dunning-notification-events/detail")]
|
||||
[Authorize(Policy = BackendPermissions.PlatformBillingNotification)]
|
||||
[EndpointSummary("查询平台催缴通知事件详情")]
|
||||
[ProducesResponseType<PlatformDunningEventItem>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PlatformDunningEventItem>> DunningEventDetail(
|
||||
[FromQuery] Guid eventId,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await platformAdminService.GetDunningEventDetailAsync(ResolveActor(), eventId, cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("dunning-notification-events/retry")]
|
||||
[Authorize(Policy = BackendPermissions.PlatformBillingNotification)]
|
||||
[EndpointSummary("重新标记平台催缴通知事件待发送")]
|
||||
[ProducesResponseType<PlatformDunningEventItem>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PlatformDunningEventItem>> RetryDunningEvent(
|
||||
RetryPlatformDunningEventDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await platformAdminService.RetryDunningEventAsync(ResolveActor(), request.ToCommand(), cancellationToken));
|
||||
}
|
||||
|
||||
private PlatformAdminActor ResolveActor()
|
||||
{
|
||||
if (currentUser.UserId is not { } userId)
|
||||
{
|
||||
throw new PlatformAdminException("Platform admin actor was not resolved.", "platform_access_denied");
|
||||
}
|
||||
|
||||
return new PlatformAdminActor(userId);
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,15 @@ public sealed class TenantAdminDirectController(
|
||||
ICurrentUser currentUser,
|
||||
ITenantContext currentTenant) : ControllerBase
|
||||
{
|
||||
[HttpGet("overview")]
|
||||
[Authorize(Policy = BackendPermissions.TenantDashboardView)]
|
||||
[EndpointSummary("查询租户后台运营概览")]
|
||||
[ProducesResponseType<TenantAdminOverviewItem>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantAdminOverviewItem>> GetOverview(CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await tenantAdminService.GetOverviewAsync(ResolveActor(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("classes")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("查询租户班级")]
|
||||
@@ -116,6 +125,117 @@ public sealed class TenantAdminDirectController(
|
||||
return Ok(await tenantAdminService.UpdateStudentStatusAsync(ResolveActor(), request.ToCommand(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("students/import/preview")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("预览学生批量导入")]
|
||||
[ProducesResponseType<TenantAdminStudentImportPreview>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantAdminStudentImportPreview>> PreviewStudentImport(
|
||||
TenantAdminStudentImportDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await tenantAdminService.PreviewStudentImportAsync(ResolveActor(), request.ToCommand(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("students/import")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("提交学生批量导入")]
|
||||
[ProducesResponseType<TenantAdminStudentImportResult>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantAdminStudentImportResult>> ImportStudents(
|
||||
TenantAdminStudentImportDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await tenantAdminService.ImportStudentsAsync(ResolveActor(), request.ToCommand(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("students/bulk-assign-class")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("批量分班")]
|
||||
[ProducesResponseType<TenantAdminBulkOperationResult>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantAdminBulkOperationResult>> BulkAssignClass(
|
||||
TenantAdminBulkAssignClassDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await tenantAdminService.BulkAssignClassAsync(ResolveActor(), request.ToCommand(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("students/bulk-status")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("批量调整学生状态")]
|
||||
[ProducesResponseType<TenantAdminBulkOperationResult>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantAdminBulkOperationResult>> BulkStudentStatus(
|
||||
TenantAdminBulkStatusDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await tenantAdminService.BulkUpdateStudentStatusAsync(ResolveActor(), request.ToCommand(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("students/supervision/rules")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("查询学习督导规则")]
|
||||
[ProducesResponseType<CatalogList<TenantSupervisionRuleItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<CatalogList<TenantSupervisionRuleItem>>> SupervisionRules(CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await tenantAdminService.GetSupervisionRulesAsync(ResolveActor(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPut("students/supervision/rules")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("新增或更新学习督导规则")]
|
||||
[ProducesResponseType<ContentManagementResult<TenantSupervisionRuleItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ContentManagementResult<TenantSupervisionRuleItem>>> UpsertSupervisionRule(
|
||||
UpsertTenantSupervisionRuleDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await tenantAdminService.UpsertSupervisionRuleAsync(ResolveActor(), request.ToCommand(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("students/supervision/preview")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("预览学习风险学生")]
|
||||
[ProducesResponseType<TenantSupervisionPreview>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantSupervisionPreview>> PreviewSupervision(CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await tenantAdminService.PreviewSupervisionAsync(ResolveActor(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("students/supervision/generate")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("批量生成学习督导跟进任务")]
|
||||
[ProducesResponseType<TenantSupervisionGenerateResult>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantSupervisionGenerateResult>> GenerateSupervision(
|
||||
TenantSupervisionGenerateDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await tenantAdminService.GenerateSupervisionFollowupsAsync(ResolveActor(), request.ToCommand(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("student-followups/report")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("查询学生跟进统计报表")]
|
||||
[ProducesResponseType<TenantFollowupReport>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantFollowupReport>> FollowupReport(CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await tenantAdminService.GetFollowupReportAsync(ResolveActor(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("feedbacks/report")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("查询反馈处理统计报表")]
|
||||
[ProducesResponseType<TenantFeedbackReport>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantFeedbackReport>> FeedbackReport(CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await tenantAdminService.GetFeedbackReportAsync(ResolveActor(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("points/risk-report")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("查询积分风险统计报表")]
|
||||
[ProducesResponseType<TenantPointRiskReport>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantPointRiskReport>> PointRiskReport(CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await tenantAdminService.GetPointRiskReportAsync(ResolveActor(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("student-notes")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("查询学生备注")]
|
||||
|
||||
@@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Tiku.Api.Contracts;
|
||||
using Tiku.Application.Commerce;
|
||||
using Tiku.Application.Jobs;
|
||||
using Tiku.Application.Security;
|
||||
using Tiku.Domain.Commerce;
|
||||
|
||||
@@ -374,6 +375,34 @@ public sealed class TenantCommerceController(
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("reconciliation/items")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("查询对账明细")]
|
||||
[ProducesResponseType<TenantReconciliationItemList>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantReconciliationItemList>> ReconciliationItems(
|
||||
[FromQuery] Guid batchId,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await commerceAdminService.GetReconciliationItemsAsync(
|
||||
ResolveActor(),
|
||||
batchId,
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("reconciliation/issues/events")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("查询对账工单事件")]
|
||||
[ProducesResponseType<TenantReconciliationIssueEventList>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantReconciliationIssueEventList>> ReconciliationIssueEvents(
|
||||
[FromQuery] Guid issueId,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await commerceAdminService.GetReconciliationIssueEventsAsync(
|
||||
ResolveActor(),
|
||||
issueId,
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("reconciliation/issues/status")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("更新对账异常状态")]
|
||||
@@ -388,6 +417,154 @@ public sealed class TenantCommerceController(
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("adjustment-vouchers")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("查询调账凭证")]
|
||||
[ProducesResponseType<TenantAdjustmentVoucherList>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantAdjustmentVoucherList>> AdjustmentVouchers(
|
||||
[FromQuery] TenantCommerceQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await commerceAdminService.GetAdjustmentVouchersAsync(
|
||||
ResolveActor(),
|
||||
ToQuery(query),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("adjustment-vouchers/detail")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("查询调账凭证详情")]
|
||||
[ProducesResponseType<CommerceAdjustmentVoucher>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<CommerceAdjustmentVoucher>> AdjustmentVoucherDetail(
|
||||
[FromQuery] Guid voucherId,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await commerceAdminService.GetAdjustmentVoucherAsync(
|
||||
ResolveActor(),
|
||||
voucherId,
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("adjustment-vouchers")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("创建调账凭证")]
|
||||
[ProducesResponseType<CommerceAdjustmentVoucher>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<CommerceAdjustmentVoucher>> CreateAdjustmentVoucher(
|
||||
CreateAdjustmentVoucherDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await commerceAdminService.CreateAdjustmentVoucherAsync(
|
||||
ResolveActor(),
|
||||
request.ToCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("adjustment-vouchers/status")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("审核或关闭调账凭证")]
|
||||
[ProducesResponseType<CommerceAdjustmentVoucher>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<CommerceAdjustmentVoucher>> UpdateAdjustmentVoucherStatus(
|
||||
UpdateAdjustmentVoucherStatusDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await commerceAdminService.UpdateAdjustmentVoucherStatusAsync(
|
||||
ResolveActor(),
|
||||
request.ToCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("adjustment-vouchers/events")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("查询调账凭证事件")]
|
||||
[ProducesResponseType<TenantAdjustmentVoucherEventList>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantAdjustmentVoucherEventList>> AdjustmentVoucherEvents(
|
||||
[FromQuery] Guid voucherId,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await commerceAdminService.GetAdjustmentVoucherEventsAsync(
|
||||
ResolveActor(),
|
||||
voucherId,
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("adjustment-vouchers/report")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("查询调账统计报告")]
|
||||
[ProducesResponseType<TenantAdjustmentReport>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantAdjustmentReport>> AdjustmentVoucherReport(CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await commerceAdminService.GetAdjustmentReportAsync(
|
||||
ResolveActor(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("reconciliation/anomalies")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("查询对账异常汇总")]
|
||||
[ProducesResponseType<TenantCommerceAnomalySummary>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantCommerceAnomalySummary>> ReconciliationAnomalies(CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await commerceAdminService.GetAnomalySummaryAsync(
|
||||
ResolveActor(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("reconciliation/preview")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("预览支付渠道账单对账")]
|
||||
[ProducesResponseType<ReconciliationImportPreview>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ReconciliationImportPreview>> PreviewReconciliation(
|
||||
PreviewReconciliationImportDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await commerceAdminService.PreviewReconciliationImportAsync(
|
||||
ResolveActor(),
|
||||
request.ToPreviewCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("reconciliation/import")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("导入支付渠道账单并创建对账批次")]
|
||||
[ProducesResponseType<CommerceReconciliationBatch>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<CommerceReconciliationBatch>> ImportReconciliation(
|
||||
PreviewReconciliationImportDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await commerceAdminService.ImportReconciliationAsync(
|
||||
ResolveActor(),
|
||||
request.ToImportCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("reconciliation/provider-bills/jobs")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("查询渠道账单下载任务")]
|
||||
[ProducesResponseType<IReadOnlyCollection<BackgroundJobItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<IReadOnlyCollection<BackgroundJobItem>>> ProviderBillJobs(
|
||||
[FromQuery] TenantCommerceQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await commerceAdminService.GetProviderBillJobsAsync(
|
||||
ResolveActor(),
|
||||
ToQuery(query),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("reconciliation/provider-bills/request")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("创建渠道官方账单下载任务")]
|
||||
[ProducesResponseType<BackgroundJobItem>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<BackgroundJobItem>> RequestProviderBill(
|
||||
RequestProviderBillJobDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await commerceAdminService.RequestProviderBillJobAsync(
|
||||
ResolveActor(),
|
||||
request.ToCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
private CommerceAdminActor ResolveActor()
|
||||
{
|
||||
if (currentTenant.TenantId is null || currentUser.UserId is null)
|
||||
|
||||
@@ -7,6 +7,7 @@ using Tiku.Application.Commerce;
|
||||
using Tiku.Application.Content;
|
||||
using Tiku.Application.Growth;
|
||||
using Tiku.Application.Points;
|
||||
using Tiku.Application.PlatformAdmin;
|
||||
using Tiku.Application.QuestionBanks;
|
||||
using Tiku.Application.Storage;
|
||||
using Tiku.Infrastructure.Content;
|
||||
@@ -235,6 +236,16 @@ public sealed class ExceptionHandlingMiddleware(
|
||||
return;
|
||||
}
|
||||
|
||||
if (exception is PlatformAdminException platformAdminException)
|
||||
{
|
||||
await WriteProblemAsync(
|
||||
context,
|
||||
platformAdminException.Message,
|
||||
PlatformAdminStatusCode(platformAdminException.Code),
|
||||
platformAdminException.Code);
|
||||
return;
|
||||
}
|
||||
|
||||
if (exception is CommerceException commerceException)
|
||||
{
|
||||
await WriteProblemAsync(
|
||||
@@ -469,6 +480,17 @@ public sealed class ExceptionHandlingMiddleware(
|
||||
};
|
||||
}
|
||||
|
||||
private static int PlatformAdminStatusCode(string code)
|
||||
{
|
||||
return code switch
|
||||
{
|
||||
"platform_access_denied" => StatusCodes.Status403Forbidden,
|
||||
"tenant_slug_exists" => StatusCodes.Status409Conflict,
|
||||
_ when code.EndsWith("_not_found", StringComparison.Ordinal) => StatusCodes.Status404NotFound,
|
||||
_ => StatusCodes.Status400BadRequest
|
||||
};
|
||||
}
|
||||
|
||||
private static int PointStatusCode(string code)
|
||||
{
|
||||
return code switch
|
||||
|
||||
Reference in New Issue
Block a user