feat(finance): add tenant settlement query backend
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Finance.Settlement.Dto;
|
||||
using TakeoutSaaS.Application.App.Finance.Settlement.Queries;
|
||||
using TakeoutSaaS.Domain.Finance.Repositories;
|
||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Finance.Settlement.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 到账汇总导出处理器。
|
||||
/// </summary>
|
||||
public sealed class ExportFinanceSettlementCsvQueryHandler(
|
||||
IFinanceTransactionRepository financeTransactionRepository,
|
||||
ITenantProvider tenantProvider)
|
||||
: IRequestHandler<ExportFinanceSettlementCsvQuery, FinanceSettlementExportDto>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<FinanceSettlementExportDto> Handle(
|
||||
ExportFinanceSettlementCsvQuery request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||
var rows = await financeTransactionRepository.ListSettlementForExportAsync(
|
||||
tenantId,
|
||||
request.StoreId,
|
||||
request.StartAt,
|
||||
request.EndAt,
|
||||
request.PaymentMethod,
|
||||
cancellationToken);
|
||||
|
||||
var list = rows.Select(FinanceSettlementMapping.ToListItem).ToList();
|
||||
var csv = BuildCsv(list);
|
||||
return new FinanceSettlementExportDto
|
||||
{
|
||||
FileName = $"settlement-{request.StoreId}-{DateTime.UtcNow:yyyyMMddHHmmss}.csv",
|
||||
FileContentBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(csv)),
|
||||
TotalCount = list.Count
|
||||
};
|
||||
}
|
||||
|
||||
private static string BuildCsv(IReadOnlyList<FinanceSettlementListItemDto> rows)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.Append('\uFEFF');
|
||||
sb.AppendLine("到账日期,支付渠道,交易笔数,到账金额");
|
||||
|
||||
foreach (var row in rows)
|
||||
{
|
||||
sb.AppendLine(string.Join(',',
|
||||
Escape(row.ArrivedDate.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)),
|
||||
Escape(row.ChannelText),
|
||||
Escape(row.TransactionCount.ToString(CultureInfo.InvariantCulture)),
|
||||
Escape(FinanceSettlementMapping.FormatAmount(row.ArrivedAmount))));
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static string Escape(string? value)
|
||||
{
|
||||
var normalized = value ?? string.Empty;
|
||||
if (normalized.Contains(',') || normalized.Contains('"') || normalized.Contains('\n'))
|
||||
{
|
||||
return $"\"{normalized.Replace("\"", "\"\"", StringComparison.Ordinal)}\"";
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
using System.Globalization;
|
||||
using TakeoutSaaS.Application.App.Finance.Settlement.Dto;
|
||||
using TakeoutSaaS.Domain.Finance.Models;
|
||||
using TakeoutSaaS.Domain.Payments.Enums;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Finance.Settlement.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 到账查询映射辅助。
|
||||
/// </summary>
|
||||
internal static class FinanceSettlementMapping
|
||||
{
|
||||
/// <summary>
|
||||
/// 支付方式转渠道编码。
|
||||
/// </summary>
|
||||
public static string ToChannelCode(PaymentMethod paymentMethod)
|
||||
{
|
||||
return paymentMethod switch
|
||||
{
|
||||
PaymentMethod.WeChatPay => "wechat",
|
||||
PaymentMethod.Alipay => "alipay",
|
||||
_ => "unknown"
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 支付方式转渠道文案。
|
||||
/// </summary>
|
||||
public static string ToChannelText(PaymentMethod paymentMethod)
|
||||
{
|
||||
return paymentMethod switch
|
||||
{
|
||||
PaymentMethod.WeChatPay => "微信支付",
|
||||
PaymentMethod.Alipay => "支付宝",
|
||||
_ => "未知渠道"
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 映射到账汇总行。
|
||||
/// </summary>
|
||||
public static FinanceSettlementListItemDto ToListItem(FinanceSettlementListItemSnapshot source)
|
||||
{
|
||||
return new FinanceSettlementListItemDto
|
||||
{
|
||||
ArrivedDate = source.ArrivedDate,
|
||||
Channel = ToChannelCode(source.PaymentMethod),
|
||||
ChannelText = ToChannelText(source.PaymentMethod),
|
||||
TransactionCount = source.TransactionCount,
|
||||
ArrivedAmount = decimal.Round(source.ArrivedAmount, 2, MidpointRounding.AwayFromZero)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 映射到账明细行。
|
||||
/// </summary>
|
||||
public static FinanceSettlementDetailItemDto ToDetailItem(FinanceSettlementDetailItemSnapshot source)
|
||||
{
|
||||
return new FinanceSettlementDetailItemDto
|
||||
{
|
||||
OrderNo = source.OrderNo,
|
||||
Amount = decimal.Round(source.Amount, 2, MidpointRounding.AwayFromZero),
|
||||
PaidAt = source.PaidAt
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 格式化金额(导出场景)。
|
||||
/// </summary>
|
||||
public static string FormatAmount(decimal value)
|
||||
{
|
||||
return decimal.Round(value, 2, MidpointRounding.AwayFromZero)
|
||||
.ToString("0.00", CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Finance.Settlement.Dto;
|
||||
using TakeoutSaaS.Application.App.Finance.Settlement.Queries;
|
||||
using TakeoutSaaS.Domain.Finance.Repositories;
|
||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Finance.Settlement.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 到账账户信息查询处理器。
|
||||
/// </summary>
|
||||
public sealed class GetFinanceSettlementAccountQueryHandler(
|
||||
IFinanceTransactionRepository financeTransactionRepository,
|
||||
ITenantProvider tenantProvider)
|
||||
: IRequestHandler<GetFinanceSettlementAccountQuery, FinanceSettlementAccountDto?>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<FinanceSettlementAccountDto?> Handle(
|
||||
GetFinanceSettlementAccountQuery request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||
var snapshot = await financeTransactionRepository.GetSettlementAccountAsync(
|
||||
tenantId,
|
||||
cancellationToken);
|
||||
|
||||
if (snapshot is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new FinanceSettlementAccountDto
|
||||
{
|
||||
BankName = snapshot.BankName,
|
||||
BankAccountName = snapshot.BankAccountName,
|
||||
BankAccountNoMasked = snapshot.BankAccountNoMasked,
|
||||
WechatMerchantNoMasked = snapshot.WechatMerchantNoMasked,
|
||||
AlipayPidMasked = snapshot.AlipayPidMasked,
|
||||
SettlementPeriodText = snapshot.SettlementPeriodText
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Finance.Settlement.Dto;
|
||||
using TakeoutSaaS.Application.App.Finance.Settlement.Queries;
|
||||
using TakeoutSaaS.Domain.Finance.Repositories;
|
||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Finance.Settlement.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 到账明细查询处理器。
|
||||
/// </summary>
|
||||
public sealed class GetFinanceSettlementDetailQueryHandler(
|
||||
IFinanceTransactionRepository financeTransactionRepository,
|
||||
ITenantProvider tenantProvider)
|
||||
: IRequestHandler<GetFinanceSettlementDetailQuery, FinanceSettlementDetailResultDto>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<FinanceSettlementDetailResultDto> Handle(
|
||||
GetFinanceSettlementDetailQuery request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||
var rows = await financeTransactionRepository.GetSettlementDetailsAsync(
|
||||
tenantId,
|
||||
request.StoreId,
|
||||
request.ArrivedDate,
|
||||
request.PaymentMethod,
|
||||
request.Take,
|
||||
cancellationToken);
|
||||
|
||||
return new FinanceSettlementDetailResultDto
|
||||
{
|
||||
Items = rows.Select(FinanceSettlementMapping.ToDetailItem).ToList()
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Finance.Settlement.Dto;
|
||||
using TakeoutSaaS.Application.App.Finance.Settlement.Queries;
|
||||
using TakeoutSaaS.Domain.Finance.Repositories;
|
||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Finance.Settlement.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 到账统计查询处理器。
|
||||
/// </summary>
|
||||
public sealed class GetFinanceSettlementStatsQueryHandler(
|
||||
IFinanceTransactionRepository financeTransactionRepository,
|
||||
ITenantProvider tenantProvider)
|
||||
: IRequestHandler<GetFinanceSettlementStatsQuery, FinanceSettlementStatsDto>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<FinanceSettlementStatsDto> Handle(
|
||||
GetFinanceSettlementStatsQuery request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||
var snapshot = await financeTransactionRepository.GetSettlementStatsAsync(
|
||||
tenantId,
|
||||
request.StoreId,
|
||||
DateTime.UtcNow,
|
||||
cancellationToken);
|
||||
|
||||
return new FinanceSettlementStatsDto
|
||||
{
|
||||
TodayArrivedAmount = snapshot.TodayArrivedAmount,
|
||||
YesterdayArrivedAmount = snapshot.YesterdayArrivedAmount,
|
||||
CurrentMonthArrivedAmount = snapshot.CurrentMonthArrivedAmount,
|
||||
CurrentMonthTransactionCount = snapshot.CurrentMonthTransactionCount
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Finance.Settlement.Dto;
|
||||
using TakeoutSaaS.Application.App.Finance.Settlement.Queries;
|
||||
using TakeoutSaaS.Domain.Finance.Repositories;
|
||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Finance.Settlement.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 到账汇总分页查询处理器。
|
||||
/// </summary>
|
||||
public sealed class SearchFinanceSettlementListQueryHandler(
|
||||
IFinanceTransactionRepository financeTransactionRepository,
|
||||
ITenantProvider tenantProvider)
|
||||
: IRequestHandler<SearchFinanceSettlementListQuery, FinanceSettlementListResultDto>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<FinanceSettlementListResultDto> Handle(
|
||||
SearchFinanceSettlementListQuery request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||
var normalizedPage = Math.Max(1, request.Page);
|
||||
var normalizedPageSize = Math.Clamp(request.PageSize, 1, 200);
|
||||
|
||||
var snapshot = await financeTransactionRepository.SearchSettlementPageAsync(
|
||||
tenantId,
|
||||
request.StoreId,
|
||||
request.StartAt,
|
||||
request.EndAt,
|
||||
request.PaymentMethod,
|
||||
normalizedPage,
|
||||
normalizedPageSize,
|
||||
cancellationToken);
|
||||
|
||||
return new FinanceSettlementListResultDto
|
||||
{
|
||||
Items = snapshot.Items.Select(FinanceSettlementMapping.ToListItem).ToList(),
|
||||
Total = snapshot.TotalCount,
|
||||
Page = normalizedPage,
|
||||
PageSize = normalizedPageSize
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user