feat: remarks

This commit is contained in:
xiaolipro 2026-04-15 16:50:57 +08:00
parent 91a8ccdecb
commit b912c5b159
8 changed files with 164 additions and 1 deletions

View File

@ -1,4 +1,5 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AClient_002Ecs_002Fl_003AC_0021_003FUsers_003FBSI_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Fc7d66b24252bb52da52d1262d99fd654ffc18aedde818f147c3daf6aee3e63_003FClient_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIAggregateRoot_002Ecs_002Fl_003AC_0021_003FUsers_003FBSI_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F6015bad29db142679ead208ae4ecd55c7800_003F0e_003F66feb813_003FIAggregateRoot_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIAggregateRoot_00601_002Ecs_002Fl_003AC_0021_003FUsers_003FBSI_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F6015bad29db142679ead208ae4ecd55c7800_003F9a_003Fea4337c7_003FIAggregateRoot_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIRepository_00601_002Ecs_002Fl_003AC_0021_003FUsers_003FBSI_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F6015bad29db142679ead208ae4ecd55c7800_003F85_003Fa6db724e_003FIRepository_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary>

View File

@ -1,12 +1,27 @@
namespace Oms.Application.Dtos.Orders;
/// <summary>
/// 订单导出入参。
/// </summary>
public class OrderExportInput
{
/// <summary>
/// 订单状态,传 <c>all</c> 或空值时表示不过滤。
/// </summary>
public string? Status { get; set; }
/// <summary>
/// 关键词,可匹配运单号、客户单号、跟踪号。
/// </summary>
public string? Keyword { get; set; }
/// <summary>
/// 创建时间起始值。
/// </summary>
public DateTime? CreateTimeStart { get; set; }
/// <summary>
/// 创建时间结束值。
/// </summary>
public DateTime? CreateTimeEnd { get; set; }
}

View File

@ -1,16 +1,37 @@
namespace Oms.Application.Dtos.Orders;
/// <summary>
/// 订单列表分页查询入参。
/// </summary>
public class OrderPaginatedInput
{
/// <summary>
/// 页码,从 1 开始。
/// </summary>
public int Page { get; set; } = 1;
/// <summary>
/// 每页条数。
/// </summary>
public int PageSize { get; set; } = 20;
/// <summary>
/// 订单状态,传 <c>all</c> 或空值时表示不过滤。
/// </summary>
public string? Status { get; set; }
/// <summary>
/// 关键词,可匹配运单号、客户单号、跟踪号。
/// </summary>
public string? Keyword { get; set; }
/// <summary>
/// 创建时间起始值。
/// </summary>
public DateTime? CreateTimeStart { get; set; }
/// <summary>
/// 创建时间结束值。
/// </summary>
public DateTime? CreateTimeEnd { get; set; }
}

View File

@ -1,10 +1,22 @@
namespace Oms.Application.Dtos.Recharge;
/// <summary>
/// 充值记录导出入参。
/// </summary>
public class RechargeExportInput
{
/// <summary>
/// 客户 ID,空值表示不过滤。
/// </summary>
public long? CustomerId { get; set; }
/// <summary>
/// 充值时间起始值。
/// </summary>
public DateTime? StartTime { get; set; }
/// <summary>
/// 充值时间结束值。
/// </summary>
public DateTime? EndTime { get; set; }
}

View File

@ -2,19 +2,43 @@ using Oms.Domain.RechargeAggregate.Enums;
namespace Oms.Application.Dtos.Recharge;
/// <summary>
/// 充值记录分页查询入参。
/// </summary>
public class RechargeRecordPaginatedInput
{
/// <summary>
/// 页码,从 1 开始。
/// </summary>
public int Page { get; set; } = 1;
/// <summary>
/// 每页条数。
/// </summary>
public int PageSize { get; set; } = 20;
/// <summary>
/// 客户 ID,空值表示不过滤。
/// </summary>
public long? CustomerId { get; set; }
/// <summary>
/// 充值流水号,支持模糊匹配。
/// </summary>
public string? SerialNumber { get; set; }
/// <summary>
/// 充值审核状态,空值表示不过滤。
/// </summary>
public RechargeStatus? RechargeStatus { get; set; }
/// <summary>
/// 充值时间起始值。
/// </summary>
public DateTime? StartTime { get; set; }
/// <summary>
/// 充值时间结束值。
/// </summary>
public DateTime? EndTime { get; set; }
}

View File

@ -25,6 +25,17 @@ public partial class OrderService : ApplicationService
private ISalesProductRepository SalesProductRepository => LazyServiceProvider.GetRequiredService<ISalesProductRepository>();
private IWebHostEnvironment WebHostEnvironment => LazyServiceProvider.GetRequiredService<IWebHostEnvironment>();
/// <summary>
/// 分页查询订单列表。
/// </summary>
/// <param name="input">入参:分页信息、订单状态、关键词、创建时间起止。</param>
/// <returns>出参:订单分页结果,包含当前页订单集合和总记录数。</returns>
/// <remarks>
/// 核心业务逻辑:
/// 1. 统一归一化页码和页大小;
/// 2. 根据状态、关键词、创建时间区间构造筛选表达式;
/// 3. 查询分页数据后补齐订单展示所需的映射字段。
/// </remarks>
[HttpPost("/order/list")]
public async Task<OmsPageResult<OrderOutput>> GetPaginatedListAsync(OrderPaginatedInput input)
{
@ -97,6 +108,14 @@ public partial class OrderService : ApplicationService
return new OrderPrintOutput { PrintUrl = printUrl };
}
/// <summary>
/// 导出订单列表。
/// </summary>
/// <param name="input">入参:订单状态、关键词、创建时间起止。</param>
/// <returns>出参:导出文件下载地址及失效时间。</returns>
/// <remarks>
/// 核心业务逻辑:先清理过期文件,再按筛选条件查询订单,最后生成 Excel 并返回下载信息。
/// </remarks>
[HttpPost("/order/export")]
public async Task<OrderExportOutput> ExportAsync(OrderExportInput input)
{
@ -159,16 +178,41 @@ public partial class OrderService : ApplicationService
return await CreateParcelCoreAsync(input);
}
/// <summary>
/// 将订单列表请求对象转换为仓储可执行的查询表达式。
/// </summary>
/// <param name="input">入参:分页查询条件对象。</param>
/// <returns>出参:订单查询表达式。</returns>
private Expression<Func<Order, bool>> BuildPaginatedPredicate(OrderPaginatedInput input)
{
return BuildOrderPredicate(input.Status, input.Keyword, input.CreateTimeStart, input.CreateTimeEnd);
}
/// <summary>
/// 将订单导出请求对象转换为仓储可执行的查询表达式。
/// </summary>
/// <param name="input">入参:导出筛选条件对象。</param>
/// <returns>出参:订单查询表达式。</returns>
private Expression<Func<Order, bool>> BuildExportPredicate(OrderExportInput input)
{
return BuildOrderPredicate(input.Status, input.Keyword, input.CreateTimeStart, input.CreateTimeEnd);
}
/// <summary>
/// 统一构造订单查询条件。
/// </summary>
/// <param name="status">入参:订单状态,传入 <c>all</c> 或空值时表示不按状态过滤。</param>
/// <param name="keyword">入参:关键词,可匹配运单号、客户单号、跟踪号。</param>
/// <param name="createTimeStart">入参:创建时间开始值。</param>
/// <param name="createTimeEnd">入参:创建时间结束值。</param>
/// <returns>出参:可直接交给 SqlSugar 仓储执行的订单筛选表达式。</returns>
/// <remarks>
/// 核心业务逻辑:
/// 1. 先把状态和关键词做统一清洗;
/// 2. 再按时间区间是否存在拆分成不同表达式;
/// 3. 避免把 <see cref="Nullable{T}.HasValue"/> / <see cref="Nullable{T}.Value"/> 放进可翻译 SQL 的表达式中,
/// 从而规避 MySQL 下时间条件被错误翻译的问题。
/// </remarks>
private static Expression<Func<Order, bool>> BuildOrderPredicate(
string? status,
string? keyword,
@ -180,6 +224,7 @@ public partial class OrderService : ApplicationService
var hasStatusFilter = !string.IsNullOrWhiteSpace(status) && status != "all";
var hasKeywordFilter = !string.IsNullOrWhiteSpace(keyword);
// 显式拆开时间分支,避免 SqlSugar 在翻译 Nullable<DateTime> 判空时生成异常 SQL。
if (createTimeStart.HasValue && createTimeEnd.HasValue)
{
var start = createTimeStart.Value;

View File

@ -106,6 +106,17 @@ public class RechargeService : ApplicationService
await RechargeRecordRepository.UpdateAsync(record);
}
/// <summary>
/// 分页查询充值记录列表。
/// </summary>
/// <param name="input">入参:分页信息、客户、充值流水号、审核状态、充值时间区间。</param>
/// <returns>出参:充值记录分页结果,包含列表数据和总记录数。</returns>
/// <remarks>
/// 核心业务逻辑:
/// 1. 先根据筛选条件查询充值记录;
/// 2. 再批量补齐客户编码和手机号等展示字段;
/// 3. 最后返回前端可直接渲染的分页结果。
/// </remarks>
[HttpGet("/recharge/recharge-record/list")]
public async Task<OmsPageResult<RechargeRecordPaginatedOutput>> GetPaginatedListAsync([FromQuery] RechargeRecordPaginatedInput input)
{
@ -127,6 +138,7 @@ public class RechargeService : ApplicationService
: await CustomerRepository.GetListAsync(x => !x.IsDeleted && customerIds.Contains(x.Id));
var customerMap = customers.ToDictionary(x => x.Id);
// 列表返回时补齐客户信息,避免前端为了展示字段额外发起关联查询。
foreach (var entity in entities)
{
var output = ObjectMapper.Map<RechargeRecord, RechargeRecordPaginatedOutput>(entity);
@ -149,6 +161,14 @@ public class RechargeService : ApplicationService
};
}
/// <summary>
/// 导出充值记录。
/// </summary>
/// <param name="input">入参:客户和充值时间区间。</param>
/// <returns>出参:Excel 文件内容。</returns>
/// <remarks>
/// 核心业务逻辑:根据导出条件查询充值记录,补齐客户展示字段后直接生成 Excel 文件流。
/// </remarks>
[HttpGet("/recharge/export")]
public async Task<FileContentResult> ExportAsync([FromQuery] RechargeExportInput input)
{
@ -189,6 +209,11 @@ public class RechargeService : ApplicationService
};
}
/// <summary>
/// 将充值分页请求对象转换为仓储可执行的查询表达式。
/// </summary>
/// <param name="input">入参:充值分页查询条件对象。</param>
/// <returns>出参:充值记录查询表达式。</returns>
private Expression<Func<RechargeRecord, bool>> BuildPaginatedPredicate(RechargeRecordPaginatedInput input)
{
var serialNumber = input.SerialNumber?.Trim();
@ -241,11 +266,31 @@ public class RechargeService : ApplicationService
};
}
/// <summary>
/// 将充值导出请求对象转换为仓储可执行的查询表达式。
/// </summary>
/// <param name="input">入参:充值导出筛选条件对象。</param>
/// <returns>出参:充值记录查询表达式。</returns>
private Expression<Func<RechargeRecord, bool>> BuildExportPredicate(RechargeExportInput input)
{
return BuildRechargePredicate(input.CustomerId, null, null, input.StartTime, input.EndTime);
}
/// <summary>
/// 统一构造充值记录查询条件。
/// </summary>
/// <param name="customerId">入参:客户 ID,空值表示不过滤。</param>
/// <param name="rechargeStatus">入参:充值状态,空值表示不过滤。</param>
/// <param name="serialNumber">入参:充值流水号关键词,空值表示不过滤。</param>
/// <param name="startTime">入参:充值时间开始值。</param>
/// <param name="endTime">入参:充值时间结束值。</param>
/// <returns>出参:可直接交给 SqlSugar 仓储执行的充值记录筛选表达式。</returns>
/// <remarks>
/// 核心业务逻辑:
/// 1. 将可选筛选条件先转换成布尔开关和值;
/// 2. 根据时间区间是否存在拆分表达式;
/// 3. 避免在 SQL 翻译阶段混用 Nullable 时间判空和比较,降低生成异常 SQL 的风险。
/// </remarks>
private static Expression<Func<RechargeRecord, bool>> BuildRechargePredicate(
long? customerId,
RechargeStatus? rechargeStatus,
@ -259,6 +304,7 @@ public class RechargeService : ApplicationService
var rechargeStatusValue = rechargeStatus.GetValueOrDefault();
var hasSerialNumberFilter = !string.IsNullOrWhiteSpace(serialNumber);
// 显式拆开时间分支,保证 ORM 只翻译纯比较表达式。
if (startTime.HasValue && endTime.HasValue)
{
var start = startTime.Value;

View File

@ -4,7 +4,6 @@
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>e3d506cf-f5ce-4ba5-bc8e-ab2c179f3034</UserSecretsId>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup>