feat: add phase five operations foundation

This commit is contained in:
2026-07-28 09:57:50 +08:00
parent 71793a2de0
commit f22f329d33
39 changed files with 4810 additions and 160 deletions

View File

@@ -15,6 +15,12 @@ public sealed record SmsProviderSendResult(
string Status,
string? MessageId = null);
public sealed class SmsProviderException(string message, string code, Exception? innerException = null)
: AuthException(code, message)
{
public Exception? ProviderException { get; } = innerException;
}
public interface ISmsProvider
{
Task<SmsProviderSendResult> SendAsync(

View File

@@ -0,0 +1,70 @@
using System.Text.Json;
using Tiku.Domain.Operations;
namespace Tiku.Application.Backoffice;
public sealed record BackofficeActor(Guid UserId, Guid? TenantId, bool IsPlatform);
public sealed record BackofficeBootstrap(
IReadOnlyCollection<BackofficePermissionItem> Permissions,
IReadOnlyCollection<BackofficeMenuItem> Menus,
IReadOnlyCollection<BackofficeRoleItem> Roles);
public sealed record BackofficePermissionItem(
Guid Id,
string Code,
string Name,
BackendPermissionArea Area,
string Module,
string? Description,
int SortOrder);
public sealed record BackofficeMenuItem(
Guid Id,
string Code,
string? ParentCode,
string Title,
BackendPermissionArea Area,
string? Path,
string? Icon,
string? PermissionCode,
int SortOrder,
bool IsActive);
public sealed record BackofficeRoleItem(
Guid Id,
string Code,
string Name,
BackendRoleStatus Status,
bool IsSystem,
string? Description,
IReadOnlyCollection<string> PermissionCodes,
IReadOnlyCollection<string> MenuCodes,
JsonElement? DataScope = null);
public sealed record UpsertBackofficeRoleCommand(
Guid? Id,
string Code,
string Name,
BackendRoleStatus Status,
string? Description,
JsonElement? DataScope = null);
public sealed record ReplaceRoleBindingsCommand(
Guid RoleId,
IReadOnlyCollection<string> PermissionCodes,
IReadOnlyCollection<string> MenuCodes);
public sealed record ReplaceUserRolesCommand(
Guid UserId,
IReadOnlyCollection<Guid> RoleIds);
public sealed record BackofficeOperationAuditCommand(
Guid? TenantId,
Guid? ActorUserId,
string Action,
string? TargetType,
string? TargetId,
JsonElement Details,
string? IpAddress = null,
string? UserAgent = null);

View File

@@ -0,0 +1,49 @@
namespace Tiku.Application.Backoffice;
public interface IBackofficeService
{
Task<BackofficeBootstrap> GetTenantBootstrapAsync(
BackofficeActor actor,
CancellationToken cancellationToken = default);
Task<BackofficeBootstrap> GetPlatformBootstrapAsync(
BackofficeActor actor,
CancellationToken cancellationToken = default);
Task<BackofficeRoleItem> UpsertTenantRoleAsync(
BackofficeActor actor,
UpsertBackofficeRoleCommand command,
CancellationToken cancellationToken = default);
Task<BackofficeRoleItem> UpsertPlatformRoleAsync(
BackofficeActor actor,
UpsertBackofficeRoleCommand command,
CancellationToken cancellationToken = default);
Task<BackofficeRoleItem> ReplaceTenantRoleBindingsAsync(
BackofficeActor actor,
ReplaceRoleBindingsCommand command,
CancellationToken cancellationToken = default);
Task<BackofficeRoleItem> ReplacePlatformRoleBindingsAsync(
BackofficeActor actor,
ReplaceRoleBindingsCommand command,
CancellationToken cancellationToken = default);
Task ReplaceTenantUserRolesAsync(
BackofficeActor actor,
ReplaceUserRolesCommand command,
CancellationToken cancellationToken = default);
Task ReplacePlatformUserRolesAsync(
BackofficeActor actor,
ReplaceUserRolesCommand command,
CancellationToken cancellationToken = default);
}
public interface IOperationAuditService
{
Task WriteAsync(
BackofficeOperationAuditCommand command,
CancellationToken cancellationToken = default);
}

View File

@@ -152,6 +152,42 @@ public sealed record TenantCouponList(IReadOnlyCollection<Coupon> Items);
public sealed record TenantCouponRedemptionList(IReadOnlyCollection<CouponRedemption> Items);
public sealed record TenantCouponReport(int CouponCount, int ClaimedCount, int UsedCount, int DiscountAppliedCents);
public sealed record CreateRefundRequestCommand(
Guid OrderId,
Guid? PaymentId,
int AmountCents,
string? Reason,
RefundEntitlementAction EntitlementAction,
JsonElement Metadata);
public sealed record UpdateRefundStatusCommand(
Guid RefundRequestId,
CommerceRefundStatus Status,
string? Reason = null,
string? ProviderRefundNo = null);
public sealed record TenantRefundList(IReadOnlyCollection<CommerceRefundRequest> Items);
public sealed record TenantRefundEventList(IReadOnlyCollection<CommerceRefundEvent> Items);
public sealed record CreateReconciliationBatchCommand(
string Provider,
DateOnly BillDate,
ReconciliationBillType BillType,
ReconciliationSource Source,
string? SourceName,
string SourceHash,
JsonElement Metadata);
public sealed record TenantReconciliationBatchList(IReadOnlyCollection<CommerceReconciliationBatch> Items);
public sealed record TenantReconciliationIssueList(IReadOnlyCollection<CommerceReconciliationIssue> Items);
public sealed record UpdateReconciliationIssueCommand(
Guid IssueId,
ReconciliationIssueStatus Status,
ReconciliationResolutionType ResolutionType,
string? Note,
Guid? AssignedTo = null);
public interface ICommerceAdminService
{
Task<IReadOnlyCollection<TenantPaymentProviderItem>> GetPaymentAccountsAsync(
@@ -248,4 +284,44 @@ public interface ICommerceAdminService
CommerceAdminActor actor,
CommerceAdminQuery query,
CancellationToken cancellationToken = default);
Task<TenantRefundList> GetRefundsAsync(
CommerceAdminActor actor,
CommerceAdminQuery query,
CancellationToken cancellationToken = default);
Task<CommerceRefundRequest> CreateRefundRequestAsync(
CommerceAdminActor actor,
CreateRefundRequestCommand command,
CancellationToken cancellationToken = default);
Task<CommerceRefundRequest> UpdateRefundStatusAsync(
CommerceAdminActor actor,
UpdateRefundStatusCommand command,
CancellationToken cancellationToken = default);
Task<TenantRefundEventList> GetRefundEventsAsync(
CommerceAdminActor actor,
Guid refundRequestId,
CancellationToken cancellationToken = default);
Task<TenantReconciliationBatchList> GetReconciliationBatchesAsync(
CommerceAdminActor actor,
CommerceAdminQuery query,
CancellationToken cancellationToken = default);
Task<CommerceReconciliationBatch> CreateReconciliationBatchAsync(
CommerceAdminActor actor,
CreateReconciliationBatchCommand command,
CancellationToken cancellationToken = default);
Task<TenantReconciliationIssueList> GetReconciliationIssuesAsync(
CommerceAdminActor actor,
CommerceAdminQuery query,
CancellationToken cancellationToken = default);
Task<CommerceReconciliationIssue> UpdateReconciliationIssueAsync(
CommerceAdminActor actor,
UpdateReconciliationIssueCommand command,
CancellationToken cancellationToken = default);
}

View File

@@ -0,0 +1,43 @@
using System.Text.Json;
using Tiku.Domain.Operations;
namespace Tiku.Application.Jobs;
public sealed record CreateBackgroundJobCommand(
Guid TenantId,
string JobType,
JsonElement Payload,
DateTimeOffset? RunAfter = null,
int MaxRetries = 3);
public sealed record BackgroundJobItem(
Guid Id,
Guid TenantId,
string JobType,
BackgroundJobStatus Status,
int RetryCount,
int MaxRetries,
DateTimeOffset? RunAfter,
DateTimeOffset? StartedAt,
DateTimeOffset? CompletedAt,
string? LastError,
Guid? OutputAssetId,
JsonElement Result);
public interface IBackgroundJobService
{
Task<BackgroundJobItem> EnqueueAsync(
CreateBackgroundJobCommand command,
CancellationToken cancellationToken = default);
Task<int> ProcessPendingAsync(
string workerId,
int batchSize,
CancellationToken cancellationToken = default);
Task<IReadOnlyCollection<BackgroundJobItem>> ListAsync(
Guid tenantId,
string? jobType = null,
int limit = 50,
CancellationToken cancellationToken = default);
}

View File

@@ -7,6 +7,7 @@ public enum TenantResolutionSource
TenantCode,
Jwt,
RefreshToken,
Worker,
System
}