feat: implement tenant member stored card module
All checks were successful
Build and Deploy TenantApi + SkuWorker / build-and-deploy (push) Successful in 2m24s
All checks were successful
Build and Deploy TenantApi + SkuWorker / build-and-deploy (push) Successful in 2m24s
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Members.StoredCard.Dto;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Members.StoredCard.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// 修改储值卡方案状态命令。
|
||||
/// </summary>
|
||||
public sealed class ChangeStoredCardPlanStatusCommand : IRequest<MemberStoredCardPlanDto>
|
||||
{
|
||||
/// <summary>
|
||||
/// 门店标识。
|
||||
/// </summary>
|
||||
public long StoreId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 方案标识。
|
||||
/// </summary>
|
||||
public long PlanId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 状态(enabled/disabled)。
|
||||
/// </summary>
|
||||
public string Status { get; init; } = "disabled";
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using MediatR;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Members.StoredCard.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// 删除储值卡方案命令。
|
||||
/// </summary>
|
||||
public sealed class DeleteStoredCardPlanCommand : IRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// 门店标识。
|
||||
/// </summary>
|
||||
public long StoreId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 方案标识。
|
||||
/// </summary>
|
||||
public long PlanId { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Members.StoredCard.Dto;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Members.StoredCard.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// 保存储值卡方案命令。
|
||||
/// </summary>
|
||||
public sealed class SaveStoredCardPlanCommand : IRequest<MemberStoredCardPlanDto>
|
||||
{
|
||||
/// <summary>
|
||||
/// 门店标识。
|
||||
/// </summary>
|
||||
public long StoreId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 方案标识(编辑时传)。
|
||||
/// </summary>
|
||||
public long? PlanId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 充值金额。
|
||||
/// </summary>
|
||||
public decimal RechargeAmount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 赠送金额。
|
||||
/// </summary>
|
||||
public decimal GiftAmount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 排序值。
|
||||
/// </summary>
|
||||
public int SortOrder { get; init; } = 100;
|
||||
|
||||
/// <summary>
|
||||
/// 状态(enabled/disabled)。
|
||||
/// </summary>
|
||||
public string Status { get; init; } = "enabled";
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Members.StoredCard.Dto;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Members.StoredCard.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// 写入储值卡充值记录命令。
|
||||
/// </summary>
|
||||
public sealed class WriteStoredCardRechargeRecordCommand : IRequest<MemberStoredCardRechargeRecordDto>
|
||||
{
|
||||
/// <summary>
|
||||
/// 门店标识。
|
||||
/// </summary>
|
||||
public long StoreId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 会员标识。
|
||||
/// </summary>
|
||||
public long MemberId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 方案标识(可空)。
|
||||
/// </summary>
|
||||
public long? PlanId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 充值金额。
|
||||
/// </summary>
|
||||
public decimal RechargeAmount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 赠送金额。
|
||||
/// </summary>
|
||||
public decimal GiftAmount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 支付方式(wechat/alipay/cash/card/balance)。
|
||||
/// </summary>
|
||||
public string PaymentMethod { get; init; } = "wechat";
|
||||
|
||||
/// <summary>
|
||||
/// 充值时间(可空,默认当前时间)。
|
||||
/// </summary>
|
||||
public DateTime? RechargedAt { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 备注。
|
||||
/// </summary>
|
||||
public string? Remark { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
namespace TakeoutSaaS.Application.App.Members.StoredCard.Dto;
|
||||
|
||||
/// <summary>
|
||||
/// 储值卡方案 DTO。
|
||||
/// </summary>
|
||||
public sealed class MemberStoredCardPlanDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 方案标识。
|
||||
/// </summary>
|
||||
public long PlanId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 充值金额。
|
||||
/// </summary>
|
||||
public decimal RechargeAmount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 赠送金额。
|
||||
/// </summary>
|
||||
public decimal GiftAmount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 到账金额(充值+赠送)。
|
||||
/// </summary>
|
||||
public decimal ArrivedAmount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 排序。
|
||||
/// </summary>
|
||||
public int SortOrder { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 状态(enabled/disabled)。
|
||||
/// </summary>
|
||||
public string Status { get; init; } = "enabled";
|
||||
|
||||
/// <summary>
|
||||
/// 累计充值次数。
|
||||
/// </summary>
|
||||
public int RechargeCount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 累计充值金额。
|
||||
/// </summary>
|
||||
public decimal TotalRechargeAmount { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace TakeoutSaaS.Application.App.Members.StoredCard.Dto;
|
||||
|
||||
/// <summary>
|
||||
/// 储值卡方案列表结果 DTO。
|
||||
/// </summary>
|
||||
public sealed class MemberStoredCardPlanListResultDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 方案列表。
|
||||
/// </summary>
|
||||
public IReadOnlyList<MemberStoredCardPlanDto> Items { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// 页面统计。
|
||||
/// </summary>
|
||||
public MemberStoredCardPlanStatsDto Stats { get; init; } = new();
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
namespace TakeoutSaaS.Application.App.Members.StoredCard.Dto;
|
||||
|
||||
/// <summary>
|
||||
/// 储值卡方案页统计 DTO。
|
||||
/// </summary>
|
||||
public sealed class MemberStoredCardPlanStatsDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 储值总额。
|
||||
/// </summary>
|
||||
public decimal TotalRechargeAmount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 赠金总额。
|
||||
/// </summary>
|
||||
public decimal TotalGiftAmount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 本月充值。
|
||||
/// </summary>
|
||||
public decimal CurrentMonthRechargeAmount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 储值用户数。
|
||||
/// </summary>
|
||||
public int RechargeMemberCount { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
namespace TakeoutSaaS.Application.App.Members.StoredCard.Dto;
|
||||
|
||||
/// <summary>
|
||||
/// 储值卡充值记录 DTO。
|
||||
/// </summary>
|
||||
public sealed class MemberStoredCardRechargeRecordDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 记录标识。
|
||||
/// </summary>
|
||||
public long RecordId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 充值单号。
|
||||
/// </summary>
|
||||
public string RecordNo { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 会员标识。
|
||||
/// </summary>
|
||||
public long MemberId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 会员名称。
|
||||
/// </summary>
|
||||
public string MemberName { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 手机号(脱敏)。
|
||||
/// </summary>
|
||||
public string MemberMobileMasked { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 充值金额。
|
||||
/// </summary>
|
||||
public decimal RechargeAmount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 赠送金额。
|
||||
/// </summary>
|
||||
public decimal GiftAmount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 到账金额。
|
||||
/// </summary>
|
||||
public decimal ArrivedAmount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 支付方式(wechat/alipay/cash/card/balance/unknown)。
|
||||
/// </summary>
|
||||
public string PaymentMethod { get; init; } = "unknown";
|
||||
|
||||
/// <summary>
|
||||
/// 充值时间(UTC)。
|
||||
/// </summary>
|
||||
public DateTime RechargedAt { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 方案标识。
|
||||
/// </summary>
|
||||
public long? PlanId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 备注。
|
||||
/// </summary>
|
||||
public string? Remark { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
namespace TakeoutSaaS.Application.App.Members.StoredCard.Dto;
|
||||
|
||||
/// <summary>
|
||||
/// 储值卡充值记录导出 DTO。
|
||||
/// </summary>
|
||||
public sealed class MemberStoredCardRechargeRecordExportDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 文件名。
|
||||
/// </summary>
|
||||
public string FileName { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 文件内容 Base64。
|
||||
/// </summary>
|
||||
public string FileContentBase64 { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 导出总数。
|
||||
/// </summary>
|
||||
public int TotalCount { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
namespace TakeoutSaaS.Application.App.Members.StoredCard.Dto;
|
||||
|
||||
/// <summary>
|
||||
/// 储值卡充值记录列表结果 DTO。
|
||||
/// </summary>
|
||||
public sealed class MemberStoredCardRechargeRecordListResultDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 列表项。
|
||||
/// </summary>
|
||||
public IReadOnlyList<MemberStoredCardRechargeRecordDto> Items { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// 页码。
|
||||
/// </summary>
|
||||
public int Page { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 每页条数。
|
||||
/// </summary>
|
||||
public int PageSize { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 总条数。
|
||||
/// </summary>
|
||||
public int TotalCount { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Members.StoredCard.Commands;
|
||||
using TakeoutSaaS.Application.App.Members.StoredCard.Dto;
|
||||
using TakeoutSaaS.Domain.Membership.Repositories;
|
||||
using TakeoutSaaS.Shared.Abstractions.Constants;
|
||||
using TakeoutSaaS.Shared.Abstractions.Exceptions;
|
||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Members.StoredCard.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 修改储值卡方案状态处理器。
|
||||
/// </summary>
|
||||
public sealed class ChangeStoredCardPlanStatusCommandHandler(
|
||||
IStoredCardRepository repository,
|
||||
ITenantProvider tenantProvider)
|
||||
: IRequestHandler<ChangeStoredCardPlanStatusCommand, MemberStoredCardPlanDto>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<MemberStoredCardPlanDto> Handle(
|
||||
ChangeStoredCardPlanStatusCommand request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||
var status = MemberStoredCardMapping.ParsePlanStatus(request.Status);
|
||||
|
||||
var entity = await repository.FindPlanByIdAsync(
|
||||
tenantId,
|
||||
request.StoreId,
|
||||
request.PlanId,
|
||||
cancellationToken)
|
||||
?? throw new BusinessException(ErrorCodes.NotFound, "储值卡方案不存在");
|
||||
|
||||
entity.Status = status;
|
||||
await repository.UpdatePlanAsync(entity, cancellationToken);
|
||||
await repository.SaveChangesAsync(cancellationToken);
|
||||
|
||||
var aggregates = await repository.GetPlanAggregatesAsync(
|
||||
tenantId,
|
||||
request.StoreId,
|
||||
[entity.Id],
|
||||
cancellationToken);
|
||||
|
||||
var aggregate = aggregates.TryGetValue(entity.Id, out var value)
|
||||
? value
|
||||
: MemberStoredCardDtoFactory.EmptyAggregate(entity.Id);
|
||||
|
||||
return MemberStoredCardDtoFactory.ToPlanDto(entity, aggregate);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Members.StoredCard.Commands;
|
||||
using TakeoutSaaS.Domain.Membership.Repositories;
|
||||
using TakeoutSaaS.Shared.Abstractions.Constants;
|
||||
using TakeoutSaaS.Shared.Abstractions.Exceptions;
|
||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Members.StoredCard.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 删除储值卡方案处理器。
|
||||
/// </summary>
|
||||
public sealed class DeleteStoredCardPlanCommandHandler(
|
||||
IStoredCardRepository repository,
|
||||
ITenantProvider tenantProvider)
|
||||
: IRequestHandler<DeleteStoredCardPlanCommand>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task Handle(DeleteStoredCardPlanCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||
|
||||
var entity = await repository.FindPlanByIdAsync(
|
||||
tenantId,
|
||||
request.StoreId,
|
||||
request.PlanId,
|
||||
cancellationToken)
|
||||
?? throw new BusinessException(ErrorCodes.NotFound, "储值卡方案不存在");
|
||||
|
||||
await repository.DeletePlanAsync(entity, cancellationToken);
|
||||
await repository.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
using System.Text;
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Members.StoredCard.Dto;
|
||||
using TakeoutSaaS.Application.App.Members.StoredCard.Queries;
|
||||
using TakeoutSaaS.Domain.Membership.Repositories;
|
||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Members.StoredCard.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 导出储值卡充值记录处理器。
|
||||
/// </summary>
|
||||
public sealed class ExportStoredCardRechargeRecordCsvQueryHandler(
|
||||
IStoredCardRepository repository,
|
||||
ITenantProvider tenantProvider)
|
||||
: IRequestHandler<ExportStoredCardRechargeRecordCsvQuery, MemberStoredCardRechargeRecordExportDto>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<MemberStoredCardRechargeRecordExportDto> Handle(
|
||||
ExportStoredCardRechargeRecordCsvQuery request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||
var normalizedKeyword = MemberStoredCardMapping.NormalizeKeyword(request.Keyword);
|
||||
var (startUtc, endUtc) = MemberStoredCardMapping.NormalizeDateRange(request.StartDateUtc, request.EndDateUtc);
|
||||
|
||||
var records = await repository.ListRechargeRecordsForExportAsync(
|
||||
tenantId,
|
||||
request.StoreId,
|
||||
startUtc,
|
||||
endUtc,
|
||||
normalizedKeyword,
|
||||
cancellationToken);
|
||||
|
||||
var csv = BuildCsv(records);
|
||||
var bytes = Encoding.UTF8.GetBytes($"\uFEFF{csv}");
|
||||
|
||||
return new MemberStoredCardRechargeRecordExportDto
|
||||
{
|
||||
FileName = $"储值卡充值记录_{DateTime.UtcNow:yyyyMMddHHmmss}.csv",
|
||||
FileContentBase64 = Convert.ToBase64String(bytes),
|
||||
TotalCount = records.Count
|
||||
};
|
||||
}
|
||||
|
||||
private static string BuildCsv(IReadOnlyCollection<Domain.Membership.Entities.MemberStoredCardRechargeRecord> records)
|
||||
{
|
||||
var lines = new List<string>
|
||||
{
|
||||
"充值单号,会员,手机号,充值金额,赠送金额,到账金额,支付方式,充值时间"
|
||||
};
|
||||
|
||||
foreach (var item in records)
|
||||
{
|
||||
lines.Add(string.Join(",",
|
||||
Escape(item.RecordNo),
|
||||
Escape(item.MemberName),
|
||||
Escape(item.MemberMobileMasked),
|
||||
item.RechargeAmount.ToString("0.00"),
|
||||
item.GiftAmount.ToString("0.00"),
|
||||
item.ArrivedAmount.ToString("0.00"),
|
||||
Escape(MemberStoredCardMapping.ToPaymentMethodDisplayText(item.PaymentMethod)),
|
||||
Escape(item.RechargedAt.ToString("yyyy-MM-dd HH:mm:ss"))));
|
||||
}
|
||||
|
||||
return string.Join('\n', lines);
|
||||
}
|
||||
|
||||
private static string Escape(string value)
|
||||
{
|
||||
var text = value.Replace("\"", "\"\"");
|
||||
return $"\"{text}\"";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Members.StoredCard.Dto;
|
||||
using TakeoutSaaS.Application.App.Members.StoredCard.Queries;
|
||||
using TakeoutSaaS.Domain.Membership.Repositories;
|
||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Members.StoredCard.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 储值卡方案列表查询处理器。
|
||||
/// </summary>
|
||||
public sealed class GetStoredCardPlanListQueryHandler(
|
||||
IStoredCardRepository repository,
|
||||
ITenantProvider tenantProvider)
|
||||
: IRequestHandler<GetStoredCardPlanListQuery, MemberStoredCardPlanListResultDto>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<MemberStoredCardPlanListResultDto> Handle(
|
||||
GetStoredCardPlanListQuery request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||
|
||||
var plans = await repository.GetPlansByStoreAsync(
|
||||
tenantId,
|
||||
request.StoreId,
|
||||
cancellationToken);
|
||||
|
||||
var planIds = plans.Select(item => item.Id).ToList();
|
||||
var aggregates = await repository.GetPlanAggregatesAsync(
|
||||
tenantId,
|
||||
request.StoreId,
|
||||
planIds,
|
||||
cancellationToken);
|
||||
|
||||
var items = plans
|
||||
.Select(item =>
|
||||
{
|
||||
var aggregate = aggregates.TryGetValue(item.Id, out var value)
|
||||
? value
|
||||
: MemberStoredCardDtoFactory.EmptyAggregate(item.Id);
|
||||
|
||||
return MemberStoredCardDtoFactory.ToPlanDto(item, aggregate);
|
||||
})
|
||||
.ToList();
|
||||
|
||||
var stats = await repository.GetPlanStatsAsync(
|
||||
tenantId,
|
||||
request.StoreId,
|
||||
DateTime.UtcNow,
|
||||
cancellationToken);
|
||||
|
||||
return new MemberStoredCardPlanListResultDto
|
||||
{
|
||||
Items = items,
|
||||
Stats = MemberStoredCardDtoFactory.ToPlanStatsDto(stats)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Members.StoredCard.Dto;
|
||||
using TakeoutSaaS.Application.App.Members.StoredCard.Queries;
|
||||
using TakeoutSaaS.Domain.Membership.Repositories;
|
||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Members.StoredCard.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 储值卡充值记录列表查询处理器。
|
||||
/// </summary>
|
||||
public sealed class GetStoredCardRechargeRecordListQueryHandler(
|
||||
IStoredCardRepository repository,
|
||||
ITenantProvider tenantProvider)
|
||||
: IRequestHandler<GetStoredCardRechargeRecordListQuery, MemberStoredCardRechargeRecordListResultDto>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<MemberStoredCardRechargeRecordListResultDto> Handle(
|
||||
GetStoredCardRechargeRecordListQuery request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||
var page = Math.Max(1, request.Page);
|
||||
var pageSize = Math.Clamp(request.PageSize, 1, 200);
|
||||
var normalizedKeyword = MemberStoredCardMapping.NormalizeKeyword(request.Keyword);
|
||||
var (startUtc, endUtc) = MemberStoredCardMapping.NormalizeDateRange(request.StartDateUtc, request.EndDateUtc);
|
||||
|
||||
var (items, totalCount) = await repository.SearchRechargeRecordsAsync(
|
||||
tenantId,
|
||||
request.StoreId,
|
||||
startUtc,
|
||||
endUtc,
|
||||
normalizedKeyword,
|
||||
page,
|
||||
pageSize,
|
||||
cancellationToken);
|
||||
|
||||
return new MemberStoredCardRechargeRecordListResultDto
|
||||
{
|
||||
Items = items.Select(MemberStoredCardDtoFactory.ToRechargeRecordDto).ToList(),
|
||||
Page = page,
|
||||
PageSize = pageSize,
|
||||
TotalCount = totalCount
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Members.StoredCard.Commands;
|
||||
using TakeoutSaaS.Application.App.Members.StoredCard.Dto;
|
||||
using TakeoutSaaS.Domain.Membership.Repositories;
|
||||
using TakeoutSaaS.Shared.Abstractions.Constants;
|
||||
using TakeoutSaaS.Shared.Abstractions.Exceptions;
|
||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Members.StoredCard.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 保存储值卡方案处理器。
|
||||
/// </summary>
|
||||
public sealed class SaveStoredCardPlanCommandHandler(
|
||||
IStoredCardRepository repository,
|
||||
ITenantProvider tenantProvider)
|
||||
: IRequestHandler<SaveStoredCardPlanCommand, MemberStoredCardPlanDto>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<MemberStoredCardPlanDto> Handle(
|
||||
SaveStoredCardPlanCommand request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||
var normalizedRechargeAmount = MemberStoredCardMapping.NormalizeRechargeAmount(request.RechargeAmount);
|
||||
var normalizedGiftAmount = MemberStoredCardMapping.NormalizeGiftAmount(request.GiftAmount);
|
||||
var normalizedSortOrder = MemberStoredCardMapping.NormalizeSortOrder(request.SortOrder);
|
||||
var status = MemberStoredCardMapping.ParsePlanStatus(request.Status);
|
||||
|
||||
var allPlans = await repository.GetPlansByStoreAsync(
|
||||
tenantId,
|
||||
request.StoreId,
|
||||
cancellationToken);
|
||||
|
||||
var duplicated = allPlans.Any(item =>
|
||||
item.Id != request.PlanId &&
|
||||
item.RechargeAmount == normalizedRechargeAmount &&
|
||||
item.GiftAmount == normalizedGiftAmount);
|
||||
|
||||
if (duplicated)
|
||||
{
|
||||
throw new BusinessException(ErrorCodes.BadRequest, "已存在相同充值金额与赠送金额的方案");
|
||||
}
|
||||
|
||||
Domain.Membership.Entities.MemberStoredCardPlan entity;
|
||||
if (request.PlanId.HasValue && request.PlanId.Value > 0)
|
||||
{
|
||||
entity = await repository.FindPlanByIdAsync(
|
||||
tenantId,
|
||||
request.StoreId,
|
||||
request.PlanId.Value,
|
||||
cancellationToken)
|
||||
?? throw new BusinessException(ErrorCodes.NotFound, "储值卡方案不存在");
|
||||
|
||||
entity.RechargeAmount = normalizedRechargeAmount;
|
||||
entity.GiftAmount = normalizedGiftAmount;
|
||||
entity.SortOrder = normalizedSortOrder;
|
||||
entity.Status = status;
|
||||
|
||||
await repository.UpdatePlanAsync(entity, cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
entity = MemberStoredCardDtoFactory.CreatePlanEntity(
|
||||
request,
|
||||
normalizedRechargeAmount,
|
||||
normalizedGiftAmount,
|
||||
normalizedSortOrder,
|
||||
status);
|
||||
|
||||
await repository.AddPlanAsync(entity, cancellationToken);
|
||||
}
|
||||
|
||||
await repository.SaveChangesAsync(cancellationToken);
|
||||
|
||||
var aggregates = await repository.GetPlanAggregatesAsync(
|
||||
tenantId,
|
||||
request.StoreId,
|
||||
[entity.Id],
|
||||
cancellationToken);
|
||||
|
||||
var aggregate = aggregates.TryGetValue(entity.Id, out var value)
|
||||
? value
|
||||
: MemberStoredCardDtoFactory.EmptyAggregate(entity.Id);
|
||||
|
||||
return MemberStoredCardDtoFactory.ToPlanDto(entity, aggregate);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Members.StoredCard.Commands;
|
||||
using TakeoutSaaS.Application.App.Members.StoredCard.Dto;
|
||||
using TakeoutSaaS.Domain.Membership.Entities;
|
||||
using TakeoutSaaS.Domain.Membership.Enums;
|
||||
using TakeoutSaaS.Domain.Membership.Repositories;
|
||||
using TakeoutSaaS.Shared.Abstractions.Constants;
|
||||
using TakeoutSaaS.Shared.Abstractions.Exceptions;
|
||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Members.StoredCard.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 写入储值卡充值记录处理器。
|
||||
/// </summary>
|
||||
public sealed class WriteStoredCardRechargeRecordCommandHandler(
|
||||
IStoredCardRepository storedCardRepository,
|
||||
IMemberRepository memberRepository,
|
||||
ITenantProvider tenantProvider)
|
||||
: IRequestHandler<WriteStoredCardRechargeRecordCommand, MemberStoredCardRechargeRecordDto>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<MemberStoredCardRechargeRecordDto> Handle(
|
||||
WriteStoredCardRechargeRecordCommand request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||
var rechargeAmount = MemberStoredCardMapping.NormalizeRechargeAmount(request.RechargeAmount);
|
||||
var giftAmount = MemberStoredCardMapping.NormalizeGiftAmount(request.GiftAmount);
|
||||
var paymentMethod = MemberStoredCardMapping.ParsePaymentMethod(request.PaymentMethod);
|
||||
var remark = MemberStoredCardMapping.NormalizeOptionalRemark(request.Remark);
|
||||
var rechargedAt = request.RechargedAt.HasValue
|
||||
? MemberStoredCardMapping.NormalizeUtc(request.RechargedAt.Value)
|
||||
: DateTime.UtcNow;
|
||||
|
||||
MemberStoredCardPlan? plan = null;
|
||||
if (request.PlanId.HasValue && request.PlanId.Value > 0)
|
||||
{
|
||||
plan = await storedCardRepository.FindPlanByIdAsync(
|
||||
tenantId,
|
||||
request.StoreId,
|
||||
request.PlanId.Value,
|
||||
cancellationToken)
|
||||
?? throw new BusinessException(ErrorCodes.NotFound, "储值卡方案不存在");
|
||||
|
||||
if (plan.Status != MemberStoredCardPlanStatus.Enabled)
|
||||
{
|
||||
throw new BusinessException(ErrorCodes.BadRequest, "储值卡方案未启用");
|
||||
}
|
||||
|
||||
if (plan.RechargeAmount != rechargeAmount || plan.GiftAmount != giftAmount)
|
||||
{
|
||||
throw new BusinessException(ErrorCodes.BadRequest, "充值金额与储值卡方案不一致");
|
||||
}
|
||||
}
|
||||
|
||||
var member = await memberRepository.FindProfileByIdAsync(
|
||||
tenantId,
|
||||
request.MemberId,
|
||||
cancellationToken)
|
||||
?? throw new BusinessException(ErrorCodes.NotFound, "会员不存在");
|
||||
|
||||
var arrivedAmount = decimal.Round(rechargeAmount + giftAmount, 2, MidpointRounding.AwayFromZero);
|
||||
var record = new MemberStoredCardRechargeRecord
|
||||
{
|
||||
StoreId = request.StoreId,
|
||||
MemberId = member.Id,
|
||||
PlanId = plan?.Id,
|
||||
RecordNo = MemberStoredCardMapping.BuildRechargeRecordNo(rechargedAt),
|
||||
MemberName = MemberStoredCardMapping.ResolveMemberName(member),
|
||||
MemberMobileMasked = MemberStoredCardMapping.ResolveMemberMobileMasked(member),
|
||||
RechargeAmount = rechargeAmount,
|
||||
GiftAmount = giftAmount,
|
||||
ArrivedAmount = arrivedAmount,
|
||||
PaymentMethod = paymentMethod,
|
||||
Remark = remark,
|
||||
RechargedAt = rechargedAt
|
||||
};
|
||||
|
||||
member.StoredRechargeBalance = decimal.Round(member.StoredRechargeBalance + rechargeAmount, 2, MidpointRounding.AwayFromZero);
|
||||
member.StoredGiftBalance = decimal.Round(member.StoredGiftBalance + giftAmount, 2, MidpointRounding.AwayFromZero);
|
||||
member.StoredBalance = decimal.Round(member.StoredRechargeBalance + member.StoredGiftBalance, 2, MidpointRounding.AwayFromZero);
|
||||
|
||||
await memberRepository.UpdateProfileAsync(member, cancellationToken);
|
||||
await storedCardRepository.AddRechargeRecordAsync(record, cancellationToken);
|
||||
await storedCardRepository.SaveChangesAsync(cancellationToken);
|
||||
|
||||
return MemberStoredCardDtoFactory.ToRechargeRecordDto(record);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
using TakeoutSaaS.Application.App.Members.StoredCard.Commands;
|
||||
using TakeoutSaaS.Application.App.Members.StoredCard.Dto;
|
||||
using TakeoutSaaS.Domain.Membership.Entities;
|
||||
using TakeoutSaaS.Domain.Membership.Enums;
|
||||
using TakeoutSaaS.Domain.Membership.Repositories;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Members.StoredCard;
|
||||
|
||||
/// <summary>
|
||||
/// 储值卡 DTO 构造器。
|
||||
/// </summary>
|
||||
internal static class MemberStoredCardDtoFactory
|
||||
{
|
||||
public static MemberStoredCardPlanAggregateSnapshot EmptyAggregate(long planId)
|
||||
{
|
||||
return new MemberStoredCardPlanAggregateSnapshot
|
||||
{
|
||||
PlanId = planId,
|
||||
RechargeCount = 0,
|
||||
TotalRechargeAmount = 0m
|
||||
};
|
||||
}
|
||||
|
||||
public static MemberStoredCardPlanDto ToPlanDto(
|
||||
MemberStoredCardPlan source,
|
||||
MemberStoredCardPlanAggregateSnapshot aggregate)
|
||||
{
|
||||
return new MemberStoredCardPlanDto
|
||||
{
|
||||
PlanId = source.Id,
|
||||
RechargeAmount = decimal.Round(source.RechargeAmount, 2, MidpointRounding.AwayFromZero),
|
||||
GiftAmount = decimal.Round(source.GiftAmount, 2, MidpointRounding.AwayFromZero),
|
||||
ArrivedAmount = decimal.Round(source.RechargeAmount + source.GiftAmount, 2, MidpointRounding.AwayFromZero),
|
||||
SortOrder = source.SortOrder,
|
||||
Status = MemberStoredCardMapping.ToPlanStatusText(source.Status),
|
||||
RechargeCount = aggregate.RechargeCount,
|
||||
TotalRechargeAmount = decimal.Round(aggregate.TotalRechargeAmount, 2, MidpointRounding.AwayFromZero)
|
||||
};
|
||||
}
|
||||
|
||||
public static MemberStoredCardPlanStatsDto ToPlanStatsDto(MemberStoredCardPlanStatsSnapshot source)
|
||||
{
|
||||
return new MemberStoredCardPlanStatsDto
|
||||
{
|
||||
TotalRechargeAmount = decimal.Round(source.TotalRechargeAmount, 2, MidpointRounding.AwayFromZero),
|
||||
TotalGiftAmount = decimal.Round(source.TotalGiftAmount, 2, MidpointRounding.AwayFromZero),
|
||||
CurrentMonthRechargeAmount = decimal.Round(source.CurrentMonthRechargeAmount, 2, MidpointRounding.AwayFromZero),
|
||||
RechargeMemberCount = source.RechargeMemberCount
|
||||
};
|
||||
}
|
||||
|
||||
public static MemberStoredCardRechargeRecordDto ToRechargeRecordDto(MemberStoredCardRechargeRecord source)
|
||||
{
|
||||
return new MemberStoredCardRechargeRecordDto
|
||||
{
|
||||
RecordId = source.Id,
|
||||
RecordNo = source.RecordNo,
|
||||
MemberId = source.MemberId,
|
||||
MemberName = source.MemberName,
|
||||
MemberMobileMasked = source.MemberMobileMasked,
|
||||
RechargeAmount = decimal.Round(source.RechargeAmount, 2, MidpointRounding.AwayFromZero),
|
||||
GiftAmount = decimal.Round(source.GiftAmount, 2, MidpointRounding.AwayFromZero),
|
||||
ArrivedAmount = decimal.Round(source.ArrivedAmount, 2, MidpointRounding.AwayFromZero),
|
||||
PaymentMethod = MemberStoredCardMapping.ToPaymentMethodText(source.PaymentMethod),
|
||||
RechargedAt = source.RechargedAt,
|
||||
PlanId = source.PlanId,
|
||||
Remark = source.Remark
|
||||
};
|
||||
}
|
||||
|
||||
public static MemberStoredCardPlan CreatePlanEntity(
|
||||
SaveStoredCardPlanCommand request,
|
||||
decimal normalizedRechargeAmount,
|
||||
decimal normalizedGiftAmount,
|
||||
int normalizedSortOrder,
|
||||
MemberStoredCardPlanStatus status)
|
||||
{
|
||||
return new MemberStoredCardPlan
|
||||
{
|
||||
StoreId = request.StoreId,
|
||||
RechargeAmount = normalizedRechargeAmount,
|
||||
GiftAmount = normalizedGiftAmount,
|
||||
SortOrder = normalizedSortOrder,
|
||||
Status = status
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
using TakeoutSaaS.Domain.Membership.Entities;
|
||||
using TakeoutSaaS.Domain.Membership.Enums;
|
||||
using TakeoutSaaS.Domain.Payments.Enums;
|
||||
using TakeoutSaaS.Shared.Abstractions.Constants;
|
||||
using TakeoutSaaS.Shared.Abstractions.Exceptions;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Members.StoredCard;
|
||||
|
||||
/// <summary>
|
||||
/// 储值卡模块映射与标准化。
|
||||
/// </summary>
|
||||
internal static class MemberStoredCardMapping
|
||||
{
|
||||
public static MemberStoredCardPlanStatus ParsePlanStatus(string? value)
|
||||
{
|
||||
var normalized = (value ?? string.Empty).Trim().ToLowerInvariant();
|
||||
return normalized switch
|
||||
{
|
||||
"enabled" => MemberStoredCardPlanStatus.Enabled,
|
||||
"disabled" => MemberStoredCardPlanStatus.Disabled,
|
||||
_ => throw new BusinessException(ErrorCodes.BadRequest, "status 参数不合法")
|
||||
};
|
||||
}
|
||||
|
||||
public static string ToPlanStatusText(MemberStoredCardPlanStatus value)
|
||||
{
|
||||
return value switch
|
||||
{
|
||||
MemberStoredCardPlanStatus.Enabled => "enabled",
|
||||
MemberStoredCardPlanStatus.Disabled => "disabled",
|
||||
_ => "disabled"
|
||||
};
|
||||
}
|
||||
|
||||
public static PaymentMethod ParsePaymentMethod(string? value)
|
||||
{
|
||||
var normalized = (value ?? string.Empty).Trim().ToLowerInvariant();
|
||||
return normalized switch
|
||||
{
|
||||
"wechat" => PaymentMethod.WeChatPay,
|
||||
"alipay" => PaymentMethod.Alipay,
|
||||
"cash" => PaymentMethod.Cash,
|
||||
"card" => PaymentMethod.Card,
|
||||
"balance" => PaymentMethod.Balance,
|
||||
_ => throw new BusinessException(ErrorCodes.BadRequest, "paymentMethod 参数不合法")
|
||||
};
|
||||
}
|
||||
|
||||
public static string ToPaymentMethodText(PaymentMethod value)
|
||||
{
|
||||
return value switch
|
||||
{
|
||||
PaymentMethod.WeChatPay => "wechat",
|
||||
PaymentMethod.Alipay => "alipay",
|
||||
PaymentMethod.Cash => "cash",
|
||||
PaymentMethod.Card => "card",
|
||||
PaymentMethod.Balance => "balance",
|
||||
_ => "unknown"
|
||||
};
|
||||
}
|
||||
|
||||
public static string ToPaymentMethodDisplayText(PaymentMethod value)
|
||||
{
|
||||
return value switch
|
||||
{
|
||||
PaymentMethod.WeChatPay => "微信支付",
|
||||
PaymentMethod.Alipay => "支付宝",
|
||||
PaymentMethod.Cash => "现金",
|
||||
PaymentMethod.Card => "刷卡",
|
||||
PaymentMethod.Balance => "余额",
|
||||
_ => "未知"
|
||||
};
|
||||
}
|
||||
|
||||
public static decimal NormalizeRechargeAmount(decimal value)
|
||||
{
|
||||
if (value <= 0)
|
||||
{
|
||||
throw new BusinessException(ErrorCodes.BadRequest, "rechargeAmount 参数不合法");
|
||||
}
|
||||
|
||||
return decimal.Round(value, 2, MidpointRounding.AwayFromZero);
|
||||
}
|
||||
|
||||
public static decimal NormalizeGiftAmount(decimal value)
|
||||
{
|
||||
if (value < 0)
|
||||
{
|
||||
throw new BusinessException(ErrorCodes.BadRequest, "giftAmount 参数不合法");
|
||||
}
|
||||
|
||||
return decimal.Round(value, 2, MidpointRounding.AwayFromZero);
|
||||
}
|
||||
|
||||
public static int NormalizeSortOrder(int value)
|
||||
{
|
||||
if (value < 0 || value > 9999)
|
||||
{
|
||||
throw new BusinessException(ErrorCodes.BadRequest, "sortOrder 参数不合法");
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public static string? NormalizeKeyword(string? value)
|
||||
{
|
||||
var normalized = (value ?? string.Empty).Trim();
|
||||
if (string.IsNullOrWhiteSpace(normalized))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (normalized.Length > 64)
|
||||
{
|
||||
throw new BusinessException(ErrorCodes.BadRequest, "keyword 长度不能超过 64");
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
public static string? NormalizeOptionalRemark(string? value)
|
||||
{
|
||||
var normalized = (value ?? string.Empty).Trim();
|
||||
if (normalized.Length == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (normalized.Length > 256)
|
||||
{
|
||||
throw new BusinessException(ErrorCodes.BadRequest, "remark 长度不能超过 256");
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
public static (DateTime? StartUtc, DateTime? EndUtc) NormalizeDateRange(DateTime? startUtc, DateTime? endUtc)
|
||||
{
|
||||
DateTime? normalizedStart = null;
|
||||
DateTime? normalizedEnd = null;
|
||||
|
||||
if (startUtc.HasValue)
|
||||
{
|
||||
var utcValue = NormalizeUtc(startUtc.Value);
|
||||
normalizedStart = new DateTime(utcValue.Year, utcValue.Month, utcValue.Day, 0, 0, 0, DateTimeKind.Utc);
|
||||
}
|
||||
|
||||
if (endUtc.HasValue)
|
||||
{
|
||||
var utcValue = NormalizeUtc(endUtc.Value);
|
||||
normalizedEnd = new DateTime(utcValue.Year, utcValue.Month, utcValue.Day, 0, 0, 0, DateTimeKind.Utc)
|
||||
.AddDays(1)
|
||||
.AddTicks(-1);
|
||||
}
|
||||
|
||||
if (normalizedStart.HasValue && normalizedEnd.HasValue && normalizedStart > normalizedEnd)
|
||||
{
|
||||
throw new BusinessException(ErrorCodes.BadRequest, "开始日期不能晚于结束日期");
|
||||
}
|
||||
|
||||
return (normalizedStart, normalizedEnd);
|
||||
}
|
||||
|
||||
public static DateTime NormalizeUtc(DateTime value)
|
||||
{
|
||||
return value.Kind switch
|
||||
{
|
||||
DateTimeKind.Utc => value,
|
||||
DateTimeKind.Local => value.ToUniversalTime(),
|
||||
_ => DateTime.SpecifyKind(value, DateTimeKind.Utc)
|
||||
};
|
||||
}
|
||||
|
||||
public static string ResolveMemberName(MemberProfile member)
|
||||
{
|
||||
var nickname = (member.Nickname ?? string.Empty).Trim();
|
||||
if (!string.IsNullOrWhiteSpace(nickname))
|
||||
{
|
||||
return nickname.Length <= 64 ? nickname : nickname[..64];
|
||||
}
|
||||
|
||||
var mobile = NormalizePhone(member.Mobile);
|
||||
return mobile.Length >= 4 ? $"会员{mobile[^4..]}" : "会员";
|
||||
}
|
||||
|
||||
public static string ResolveMemberMobileMasked(MemberProfile member)
|
||||
{
|
||||
return MaskPhone(NormalizePhone(member.Mobile));
|
||||
}
|
||||
|
||||
public static string BuildRechargeRecordNo(DateTime nowUtc)
|
||||
{
|
||||
var utcNow = NormalizeUtc(nowUtc);
|
||||
return $"CZ{utcNow:yyyyMMddHHmmssfff}{Random.Shared.Next(1000, 9999)}";
|
||||
}
|
||||
|
||||
private static string NormalizePhone(string? value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var chars = value.Where(char.IsDigit).ToArray();
|
||||
return chars.Length == 0 ? string.Empty : new string(chars);
|
||||
}
|
||||
|
||||
private static string MaskPhone(string normalizedPhone)
|
||||
{
|
||||
if (normalizedPhone.Length >= 11)
|
||||
{
|
||||
return $"{normalizedPhone[..3]}****{normalizedPhone[^4..]}";
|
||||
}
|
||||
|
||||
if (normalizedPhone.Length >= 7)
|
||||
{
|
||||
return $"{normalizedPhone[..3]}***{normalizedPhone[^2..]}";
|
||||
}
|
||||
|
||||
return normalizedPhone;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Members.StoredCard.Dto;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Members.StoredCard.Queries;
|
||||
|
||||
/// <summary>
|
||||
/// 导出储值卡充值记录 CSV。
|
||||
/// </summary>
|
||||
public sealed class ExportStoredCardRechargeRecordCsvQuery : IRequest<MemberStoredCardRechargeRecordExportDto>
|
||||
{
|
||||
/// <summary>
|
||||
/// 门店标识。
|
||||
/// </summary>
|
||||
public long StoreId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 开始日期(UTC)。
|
||||
/// </summary>
|
||||
public DateTime? StartDateUtc { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 结束日期(UTC)。
|
||||
/// </summary>
|
||||
public DateTime? EndDateUtc { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 关键字(会员/手机号/单号)。
|
||||
/// </summary>
|
||||
public string? Keyword { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Members.StoredCard.Dto;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Members.StoredCard.Queries;
|
||||
|
||||
/// <summary>
|
||||
/// 查询储值卡方案列表。
|
||||
/// </summary>
|
||||
public sealed class GetStoredCardPlanListQuery : IRequest<MemberStoredCardPlanListResultDto>
|
||||
{
|
||||
/// <summary>
|
||||
/// 门店标识。
|
||||
/// </summary>
|
||||
public long StoreId { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Members.StoredCard.Dto;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Members.StoredCard.Queries;
|
||||
|
||||
/// <summary>
|
||||
/// 查询储值卡充值记录分页。
|
||||
/// </summary>
|
||||
public sealed class GetStoredCardRechargeRecordListQuery : IRequest<MemberStoredCardRechargeRecordListResultDto>
|
||||
{
|
||||
/// <summary>
|
||||
/// 门店标识。
|
||||
/// </summary>
|
||||
public long StoreId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 开始日期(UTC)。
|
||||
/// </summary>
|
||||
public DateTime? StartDateUtc { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 结束日期(UTC)。
|
||||
/// </summary>
|
||||
public DateTime? EndDateUtc { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 关键字(会员/手机号/单号)。
|
||||
/// </summary>
|
||||
public string? Keyword { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 页码。
|
||||
/// </summary>
|
||||
public int Page { get; init; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// 每页条数。
|
||||
/// </summary>
|
||||
public int PageSize { get; init; } = 8;
|
||||
}
|
||||
Reference in New Issue
Block a user