feat: 新增财务交易流水后端模块
This commit is contained in:
@@ -0,0 +1,256 @@
|
||||
namespace TakeoutSaaS.Application.App.Finance.Transactions.Dto;
|
||||
|
||||
/// <summary>
|
||||
/// 交易流水列表行。
|
||||
/// </summary>
|
||||
public sealed class FinanceTransactionListItemDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 交易标识。
|
||||
/// </summary>
|
||||
public string TransactionId { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 流水号。
|
||||
/// </summary>
|
||||
public string TransactionNo { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 关联订单号。
|
||||
/// </summary>
|
||||
public string? OrderNo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 交易类型编码。
|
||||
/// </summary>
|
||||
public string TransactionType { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 交易类型文案。
|
||||
/// </summary>
|
||||
public string TransactionTypeText { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 渠道文案。
|
||||
/// </summary>
|
||||
public string ChannelText { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 支付方式文案。
|
||||
/// </summary>
|
||||
public string PaymentMethodText { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 交易金额(带符号)。
|
||||
/// </summary>
|
||||
public decimal AmountSigned { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 交易时间。
|
||||
/// </summary>
|
||||
public DateTime OccurredAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 备注。
|
||||
/// </summary>
|
||||
public string Remark { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 是否收入。
|
||||
/// </summary>
|
||||
public bool IsIncome { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 交易流水列表结果。
|
||||
/// </summary>
|
||||
public sealed class FinanceTransactionListResultDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 分页数据。
|
||||
/// </summary>
|
||||
public List<FinanceTransactionListItemDto> Items { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// 总数。
|
||||
/// </summary>
|
||||
public int Total { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前页码。
|
||||
/// </summary>
|
||||
public int Page { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 每页条数。
|
||||
/// </summary>
|
||||
public int PageSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 本页收入合计。
|
||||
/// </summary>
|
||||
public decimal PageIncomeAmount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 本页退款合计。
|
||||
/// </summary>
|
||||
public decimal PageRefundAmount { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 交易流水统计。
|
||||
/// </summary>
|
||||
public sealed class FinanceTransactionStatsDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 总收入。
|
||||
/// </summary>
|
||||
public decimal TotalIncome { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 总退款。
|
||||
/// </summary>
|
||||
public decimal TotalRefund { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 交易笔数。
|
||||
/// </summary>
|
||||
public int TotalCount { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 交易流水详情。
|
||||
/// </summary>
|
||||
public sealed class FinanceTransactionDetailDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 交易标识。
|
||||
/// </summary>
|
||||
public string TransactionId { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 流水号。
|
||||
/// </summary>
|
||||
public string TransactionNo { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 交易类型编码。
|
||||
/// </summary>
|
||||
public string TransactionType { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 交易类型文案。
|
||||
/// </summary>
|
||||
public string TransactionTypeText { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 门店标识。
|
||||
/// </summary>
|
||||
public long StoreId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 关联订单号。
|
||||
/// </summary>
|
||||
public string? OrderNo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 渠道文案。
|
||||
/// </summary>
|
||||
public string ChannelText { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 支付方式文案。
|
||||
/// </summary>
|
||||
public string PaymentMethodText { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 交易金额(带符号)。
|
||||
/// </summary>
|
||||
public decimal AmountSigned { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 交易时间。
|
||||
/// </summary>
|
||||
public DateTime OccurredAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 备注。
|
||||
/// </summary>
|
||||
public string Remark { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 顾客姓名。
|
||||
/// </summary>
|
||||
public string CustomerName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 顾客手机号。
|
||||
/// </summary>
|
||||
public string CustomerPhone { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 退款单号。
|
||||
/// </summary>
|
||||
public string? RefundNo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 退款原因。
|
||||
/// </summary>
|
||||
public string? RefundReason { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 会员名称。
|
||||
/// </summary>
|
||||
public string? MemberName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 会员手机号脱敏值。
|
||||
/// </summary>
|
||||
public string? MemberMobileMasked { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 充值金额。
|
||||
/// </summary>
|
||||
public decimal? RechargeAmount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 赠送金额。
|
||||
/// </summary>
|
||||
public decimal? GiftAmount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 到账金额。
|
||||
/// </summary>
|
||||
public decimal? ArrivedAmount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 积分变动值。
|
||||
/// </summary>
|
||||
public int? PointChangeAmount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 积分变动后余额。
|
||||
/// </summary>
|
||||
public int? PointBalanceAfterChange { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 交易流水导出结果。
|
||||
/// </summary>
|
||||
public sealed class FinanceTransactionExportDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 文件名。
|
||||
/// </summary>
|
||||
public string FileName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 文件内容(Base64)。
|
||||
/// </summary>
|
||||
public string FileContentBase64 { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 导出总数。
|
||||
/// </summary>
|
||||
public int TotalCount { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Finance.Transactions.Dto;
|
||||
using TakeoutSaaS.Application.App.Finance.Transactions.Queries;
|
||||
using TakeoutSaaS.Domain.Finance.Repositories;
|
||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Finance.Transactions.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 交易流水 CSV 导出查询处理器。
|
||||
/// </summary>
|
||||
public sealed class ExportFinanceTransactionCsvQueryHandler(
|
||||
IFinanceTransactionRepository financeTransactionRepository,
|
||||
ITenantProvider tenantProvider)
|
||||
: IRequestHandler<ExportFinanceTransactionCsvQuery, FinanceTransactionExportDto>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<FinanceTransactionExportDto> Handle(ExportFinanceTransactionCsvQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 按筛选读取导出数据。
|
||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||
var records = await financeTransactionRepository.ListForExportAsync(
|
||||
tenantId,
|
||||
request.StoreId,
|
||||
request.StartAt,
|
||||
request.EndAt,
|
||||
request.TransactionType,
|
||||
request.DeliveryType,
|
||||
request.PaymentMethod,
|
||||
request.Keyword,
|
||||
cancellationToken);
|
||||
|
||||
// 2. 组装 CSV 并输出 Base64。
|
||||
var csv = BuildCsv(records.Select(FinanceTransactionMapping.ToListItem).ToList());
|
||||
var bytes = Encoding.UTF8.GetPreamble().Concat(Encoding.UTF8.GetBytes(csv)).ToArray();
|
||||
|
||||
return new FinanceTransactionExportDto
|
||||
{
|
||||
FileName = $"交易流水_{DateTime.UtcNow:yyyyMMddHHmmss}.csv",
|
||||
FileContentBase64 = Convert.ToBase64String(bytes),
|
||||
TotalCount = records.Count
|
||||
};
|
||||
}
|
||||
|
||||
private static string BuildCsv(IReadOnlyList<FinanceTransactionListItemDto> rows)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
builder.AppendLine("流水号,关联订单,类型,渠道,支付方式,金额,交易时间,备注");
|
||||
|
||||
foreach (var row in rows)
|
||||
{
|
||||
var cells = new[]
|
||||
{
|
||||
Escape(row.TransactionNo),
|
||||
Escape(string.IsNullOrWhiteSpace(row.OrderNo) ? "—" : row.OrderNo!),
|
||||
Escape(row.TransactionTypeText),
|
||||
Escape(row.ChannelText),
|
||||
Escape(row.PaymentMethodText),
|
||||
Escape(FinanceTransactionMapping.FormatAmount(row.AmountSigned)),
|
||||
Escape(row.OccurredAt.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture)),
|
||||
Escape(string.IsNullOrWhiteSpace(row.Remark) ? "—" : row.Remark)
|
||||
};
|
||||
|
||||
builder.AppendLine(string.Join(',', cells));
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private static string Escape(string value)
|
||||
{
|
||||
if (!value.Contains('"') && !value.Contains(',') && !value.Contains('\n') && !value.Contains('\r'))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
return $"\"{value.Replace("\"", "\"\"")}\"";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
using System.Globalization;
|
||||
using TakeoutSaaS.Application.App.Finance.Transactions.Dto;
|
||||
using TakeoutSaaS.Domain.Finance.Enums;
|
||||
using TakeoutSaaS.Domain.Finance.Models;
|
||||
using TakeoutSaaS.Domain.Orders.Enums;
|
||||
using TakeoutSaaS.Domain.Payments.Enums;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Finance.Transactions.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 交易流水映射与文案转换。
|
||||
/// </summary>
|
||||
internal static class FinanceTransactionMapping
|
||||
{
|
||||
/// <summary>
|
||||
/// 生成交易复合标识。
|
||||
/// </summary>
|
||||
public static string BuildTransactionId(FinanceTransactionSourceType sourceType, long sourceId)
|
||||
{
|
||||
var sourceCode = sourceType switch
|
||||
{
|
||||
FinanceTransactionSourceType.PaymentRecord => "payment",
|
||||
FinanceTransactionSourceType.PaymentRefundRecord => "payment_refund",
|
||||
FinanceTransactionSourceType.RefundRequest => "refund_request",
|
||||
FinanceTransactionSourceType.StoredCardRechargeRecord => "stored_card_recharge",
|
||||
FinanceTransactionSourceType.MemberPointLedger => "member_point",
|
||||
_ => "unknown"
|
||||
};
|
||||
|
||||
return $"{sourceCode}:{sourceId}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解析交易类型编码。
|
||||
/// </summary>
|
||||
public static string ToTransactionTypeCode(FinanceTransactionType transactionType)
|
||||
{
|
||||
return transactionType switch
|
||||
{
|
||||
FinanceTransactionType.Income => "income",
|
||||
FinanceTransactionType.Refund => "refund",
|
||||
FinanceTransactionType.StoredCardRecharge => "stored_card_recharge",
|
||||
FinanceTransactionType.PointRedeem => "point_redeem",
|
||||
_ => "unknown"
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解析交易类型文案。
|
||||
/// </summary>
|
||||
public static string ToTransactionTypeText(FinanceTransactionType transactionType)
|
||||
{
|
||||
return transactionType switch
|
||||
{
|
||||
FinanceTransactionType.Income => "收入",
|
||||
FinanceTransactionType.Refund => "退款",
|
||||
FinanceTransactionType.StoredCardRecharge => "储值充值",
|
||||
FinanceTransactionType.PointRedeem => "积分抵扣",
|
||||
_ => "未知"
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解析渠道文案。
|
||||
/// </summary>
|
||||
public static string ToChannelText(DeliveryType? deliveryType)
|
||||
{
|
||||
return deliveryType switch
|
||||
{
|
||||
DeliveryType.Delivery => "外卖",
|
||||
DeliveryType.Pickup => "自提",
|
||||
DeliveryType.DineIn => "堂食",
|
||||
_ => "—"
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解析支付方式文案。
|
||||
/// </summary>
|
||||
public static string ToPaymentMethodText(PaymentMethod? paymentMethod)
|
||||
{
|
||||
return paymentMethod switch
|
||||
{
|
||||
PaymentMethod.WeChatPay => "微信",
|
||||
PaymentMethod.Alipay => "支付宝",
|
||||
PaymentMethod.Cash => "现金",
|
||||
PaymentMethod.Card => "刷卡",
|
||||
PaymentMethod.Balance => "储值余额",
|
||||
_ => "—"
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 映射列表行。
|
||||
/// </summary>
|
||||
public static FinanceTransactionListItemDto ToListItem(FinanceTransactionRecord source)
|
||||
{
|
||||
return new FinanceTransactionListItemDto
|
||||
{
|
||||
TransactionId = BuildTransactionId(source.SourceType, source.SourceId),
|
||||
TransactionNo = source.TransactionNo ?? string.Empty,
|
||||
OrderNo = source.OrderNo,
|
||||
TransactionType = ToTransactionTypeCode(source.TransactionType),
|
||||
TransactionTypeText = ToTransactionTypeText(source.TransactionType),
|
||||
ChannelText = ToChannelText(source.DeliveryType),
|
||||
PaymentMethodText = ToPaymentMethodText(source.PaymentMethod),
|
||||
AmountSigned = decimal.Round(source.AmountSigned, 2, MidpointRounding.AwayFromZero),
|
||||
OccurredAt = source.OccurredAt,
|
||||
Remark = string.IsNullOrWhiteSpace(source.Remark) ? "—" : source.Remark.Trim(),
|
||||
IsIncome = source.AmountSigned > 0
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 映射详情。
|
||||
/// </summary>
|
||||
public static FinanceTransactionDetailDto ToDetail(FinanceTransactionRecord source)
|
||||
{
|
||||
return new FinanceTransactionDetailDto
|
||||
{
|
||||
TransactionId = BuildTransactionId(source.SourceType, source.SourceId),
|
||||
TransactionNo = source.TransactionNo ?? string.Empty,
|
||||
TransactionType = ToTransactionTypeCode(source.TransactionType),
|
||||
TransactionTypeText = ToTransactionTypeText(source.TransactionType),
|
||||
StoreId = source.StoreId,
|
||||
OrderNo = source.OrderNo,
|
||||
ChannelText = ToChannelText(source.DeliveryType),
|
||||
PaymentMethodText = ToPaymentMethodText(source.PaymentMethod),
|
||||
AmountSigned = decimal.Round(source.AmountSigned, 2, MidpointRounding.AwayFromZero),
|
||||
OccurredAt = source.OccurredAt,
|
||||
Remark = string.IsNullOrWhiteSpace(source.Remark) ? "—" : source.Remark.Trim(),
|
||||
CustomerName = string.IsNullOrWhiteSpace(source.CustomerName) ? "—" : source.CustomerName.Trim(),
|
||||
CustomerPhone = string.IsNullOrWhiteSpace(source.CustomerPhone) ? "—" : source.CustomerPhone.Trim(),
|
||||
RefundNo = source.RefundNo,
|
||||
RefundReason = source.RefundReason,
|
||||
MemberName = source.MemberName,
|
||||
MemberMobileMasked = source.MemberMobileMasked,
|
||||
RechargeAmount = source.RechargeAmount,
|
||||
GiftAmount = source.GiftAmount,
|
||||
ArrivedAmount = source.ArrivedAmount,
|
||||
PointChangeAmount = source.PointChangeAmount,
|
||||
PointBalanceAfterChange = source.PointBalanceAfterChange
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 导出金额文本。
|
||||
/// </summary>
|
||||
public static string FormatAmount(decimal amountSigned)
|
||||
{
|
||||
var rounded = decimal.Round(amountSigned, 2, MidpointRounding.AwayFromZero);
|
||||
var sign = rounded >= 0 ? "+" : string.Empty;
|
||||
return $"{sign}{rounded.ToString("0.00", CultureInfo.InvariantCulture)}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Finance.Transactions.Dto;
|
||||
using TakeoutSaaS.Application.App.Finance.Transactions.Queries;
|
||||
using TakeoutSaaS.Domain.Finance.Repositories;
|
||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Finance.Transactions.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 交易流水详情查询处理器。
|
||||
/// </summary>
|
||||
public sealed class GetFinanceTransactionDetailQueryHandler(
|
||||
IFinanceTransactionRepository financeTransactionRepository,
|
||||
ITenantProvider tenantProvider)
|
||||
: IRequestHandler<GetFinanceTransactionDetailQuery, FinanceTransactionDetailDto?>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<FinanceTransactionDetailDto?> Handle(GetFinanceTransactionDetailQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 读取租户上下文并查询详情。
|
||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||
var record = await financeTransactionRepository.GetDetailAsync(
|
||||
tenantId,
|
||||
request.StoreId,
|
||||
request.SourceType,
|
||||
request.SourceId,
|
||||
cancellationToken);
|
||||
|
||||
// 2. 映射详情输出。
|
||||
return record is null ? null : FinanceTransactionMapping.ToDetail(record);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Finance.Transactions.Dto;
|
||||
using TakeoutSaaS.Application.App.Finance.Transactions.Queries;
|
||||
using TakeoutSaaS.Domain.Finance.Repositories;
|
||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Finance.Transactions.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 交易流水统计查询处理器。
|
||||
/// </summary>
|
||||
public sealed class GetFinanceTransactionStatsQueryHandler(
|
||||
IFinanceTransactionRepository financeTransactionRepository,
|
||||
ITenantProvider tenantProvider)
|
||||
: IRequestHandler<GetFinanceTransactionStatsQuery, FinanceTransactionStatsDto>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<FinanceTransactionStatsDto> Handle(GetFinanceTransactionStatsQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 读取租户上下文并执行统计查询。
|
||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||
var snapshot = await financeTransactionRepository.GetStatsAsync(
|
||||
tenantId,
|
||||
request.StoreId,
|
||||
request.StartAt,
|
||||
request.EndAt,
|
||||
request.TransactionType,
|
||||
request.DeliveryType,
|
||||
request.PaymentMethod,
|
||||
request.Keyword,
|
||||
cancellationToken);
|
||||
|
||||
// 2. 映射统计结果。
|
||||
return new FinanceTransactionStatsDto
|
||||
{
|
||||
TotalIncome = snapshot.TotalIncome,
|
||||
TotalRefund = snapshot.TotalRefund,
|
||||
TotalCount = snapshot.TotalCount
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Finance.Transactions.Dto;
|
||||
using TakeoutSaaS.Application.App.Finance.Transactions.Queries;
|
||||
using TakeoutSaaS.Domain.Finance.Repositories;
|
||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Finance.Transactions.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 交易流水列表查询处理器。
|
||||
/// </summary>
|
||||
public sealed class SearchFinanceTransactionListQueryHandler(
|
||||
IFinanceTransactionRepository financeTransactionRepository,
|
||||
ITenantProvider tenantProvider)
|
||||
: IRequestHandler<SearchFinanceTransactionListQuery, FinanceTransactionListResultDto>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<FinanceTransactionListResultDto> Handle(SearchFinanceTransactionListQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 读取租户上下文并执行分页查询。
|
||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||
var page = Math.Max(1, request.Page);
|
||||
var pageSize = Math.Clamp(request.PageSize, 1, 200);
|
||||
|
||||
var snapshot = await financeTransactionRepository.SearchPageAsync(
|
||||
tenantId,
|
||||
request.StoreId,
|
||||
request.StartAt,
|
||||
request.EndAt,
|
||||
request.TransactionType,
|
||||
request.DeliveryType,
|
||||
request.PaymentMethod,
|
||||
request.Keyword,
|
||||
page,
|
||||
pageSize,
|
||||
cancellationToken);
|
||||
|
||||
// 2. 映射结果并返回。
|
||||
return new FinanceTransactionListResultDto
|
||||
{
|
||||
Items = snapshot.Items.Select(FinanceTransactionMapping.ToListItem).ToList(),
|
||||
Total = snapshot.TotalCount,
|
||||
Page = page,
|
||||
PageSize = pageSize,
|
||||
PageIncomeAmount = snapshot.PageIncomeAmount,
|
||||
PageRefundAmount = snapshot.PageRefundAmount
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Finance.Transactions.Dto;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Finance.Transactions.Queries;
|
||||
|
||||
/// <summary>
|
||||
/// 交易流水 CSV 导出查询。
|
||||
/// </summary>
|
||||
public sealed class ExportFinanceTransactionCsvQuery : FinanceTransactionFilterQueryBase, IRequest<FinanceTransactionExportDto>
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using TakeoutSaaS.Domain.Finance.Enums;
|
||||
using TakeoutSaaS.Domain.Orders.Enums;
|
||||
using TakeoutSaaS.Domain.Payments.Enums;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Finance.Transactions.Queries;
|
||||
|
||||
/// <summary>
|
||||
/// 交易流水筛选查询基类。
|
||||
/// </summary>
|
||||
public abstract class FinanceTransactionFilterQueryBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 门店 ID。
|
||||
/// </summary>
|
||||
public long StoreId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 开始时间(含)。
|
||||
/// </summary>
|
||||
public DateTime? StartAt { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 结束时间(不含)。
|
||||
/// </summary>
|
||||
public DateTime? EndAt { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 交易类型。
|
||||
/// </summary>
|
||||
public FinanceTransactionType? TransactionType { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 渠道。
|
||||
/// </summary>
|
||||
public DeliveryType? DeliveryType { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 支付方式。
|
||||
/// </summary>
|
||||
public PaymentMethod? PaymentMethod { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 关键词。
|
||||
/// </summary>
|
||||
public string? Keyword { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Finance.Transactions.Dto;
|
||||
using TakeoutSaaS.Domain.Finance.Enums;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Finance.Transactions.Queries;
|
||||
|
||||
/// <summary>
|
||||
/// 交易流水详情查询。
|
||||
/// </summary>
|
||||
public sealed class GetFinanceTransactionDetailQuery : IRequest<FinanceTransactionDetailDto?>
|
||||
{
|
||||
/// <summary>
|
||||
/// 门店 ID。
|
||||
/// </summary>
|
||||
public long StoreId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 来源类型。
|
||||
/// </summary>
|
||||
public FinanceTransactionSourceType SourceType { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 来源标识。
|
||||
/// </summary>
|
||||
public long SourceId { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Finance.Transactions.Dto;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Finance.Transactions.Queries;
|
||||
|
||||
/// <summary>
|
||||
/// 交易流水统计查询。
|
||||
/// </summary>
|
||||
public sealed class GetFinanceTransactionStatsQuery : FinanceTransactionFilterQueryBase, IRequest<FinanceTransactionStatsDto>
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Finance.Transactions.Dto;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Finance.Transactions.Queries;
|
||||
|
||||
/// <summary>
|
||||
/// 交易流水列表查询。
|
||||
/// </summary>
|
||||
public sealed class SearchFinanceTransactionListQuery : FinanceTransactionFilterQueryBase, IRequest<FinanceTransactionListResultDto>
|
||||
{
|
||||
/// <summary>
|
||||
/// 页码。
|
||||
/// </summary>
|
||||
public int Page { get; init; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// 每页条数。
|
||||
/// </summary>
|
||||
public int PageSize { get; init; } = 20;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using FluentValidation;
|
||||
using TakeoutSaaS.Application.App.Finance.Transactions.Queries;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Finance.Transactions.Validators;
|
||||
|
||||
/// <summary>
|
||||
/// 交易流水导出查询验证器。
|
||||
/// </summary>
|
||||
public sealed class ExportFinanceTransactionCsvQueryValidator : AbstractValidator<ExportFinanceTransactionCsvQuery>
|
||||
{
|
||||
/// <summary>
|
||||
/// 初始化验证规则。
|
||||
/// </summary>
|
||||
public ExportFinanceTransactionCsvQueryValidator()
|
||||
{
|
||||
RuleFor(x => x.StoreId).GreaterThan(0);
|
||||
RuleFor(x => x.Keyword).MaximumLength(64);
|
||||
RuleFor(x => x)
|
||||
.Must(x => !x.StartAt.HasValue || !x.EndAt.HasValue || x.StartAt < x.EndAt)
|
||||
.WithMessage("开始时间必须早于结束时间");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using FluentValidation;
|
||||
using TakeoutSaaS.Application.App.Finance.Transactions.Queries;
|
||||
using TakeoutSaaS.Domain.Finance.Enums;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Finance.Transactions.Validators;
|
||||
|
||||
/// <summary>
|
||||
/// 交易流水详情查询验证器。
|
||||
/// </summary>
|
||||
public sealed class GetFinanceTransactionDetailQueryValidator : AbstractValidator<GetFinanceTransactionDetailQuery>
|
||||
{
|
||||
/// <summary>
|
||||
/// 初始化验证规则。
|
||||
/// </summary>
|
||||
public GetFinanceTransactionDetailQueryValidator()
|
||||
{
|
||||
RuleFor(x => x.StoreId).GreaterThan(0);
|
||||
RuleFor(x => x.SourceId).GreaterThan(0);
|
||||
RuleFor(x => x.SourceType)
|
||||
.Must(x => x is FinanceTransactionSourceType.PaymentRecord
|
||||
or FinanceTransactionSourceType.PaymentRefundRecord
|
||||
or FinanceTransactionSourceType.RefundRequest
|
||||
or FinanceTransactionSourceType.StoredCardRechargeRecord
|
||||
or FinanceTransactionSourceType.MemberPointLedger)
|
||||
.WithMessage("sourceType 非法");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using FluentValidation;
|
||||
using TakeoutSaaS.Application.App.Finance.Transactions.Queries;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Finance.Transactions.Validators;
|
||||
|
||||
/// <summary>
|
||||
/// 交易流水统计查询验证器。
|
||||
/// </summary>
|
||||
public sealed class GetFinanceTransactionStatsQueryValidator : AbstractValidator<GetFinanceTransactionStatsQuery>
|
||||
{
|
||||
/// <summary>
|
||||
/// 初始化验证规则。
|
||||
/// </summary>
|
||||
public GetFinanceTransactionStatsQueryValidator()
|
||||
{
|
||||
RuleFor(x => x.StoreId).GreaterThan(0);
|
||||
RuleFor(x => x.Keyword).MaximumLength(64);
|
||||
RuleFor(x => x)
|
||||
.Must(x => !x.StartAt.HasValue || !x.EndAt.HasValue || x.StartAt < x.EndAt)
|
||||
.WithMessage("开始时间必须早于结束时间");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using FluentValidation;
|
||||
using TakeoutSaaS.Application.App.Finance.Transactions.Queries;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Finance.Transactions.Validators;
|
||||
|
||||
/// <summary>
|
||||
/// 交易流水列表查询验证器。
|
||||
/// </summary>
|
||||
public sealed class SearchFinanceTransactionListQueryValidator : AbstractValidator<SearchFinanceTransactionListQuery>
|
||||
{
|
||||
/// <summary>
|
||||
/// 初始化验证规则。
|
||||
/// </summary>
|
||||
public SearchFinanceTransactionListQueryValidator()
|
||||
{
|
||||
RuleFor(x => x.StoreId).GreaterThan(0);
|
||||
RuleFor(x => x.Page).GreaterThan(0);
|
||||
RuleFor(x => x.PageSize).InclusiveBetween(1, 200);
|
||||
RuleFor(x => x.Keyword).MaximumLength(64);
|
||||
RuleFor(x => x)
|
||||
.Must(x => !x.StartAt.HasValue || !x.EndAt.HasValue || x.StartAt < x.EndAt)
|
||||
.WithMessage("开始时间必须早于结束时间");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user