feat(order): 修复列表状态筛选、收件国家名与小包轨迹同步
改用仓储链式 Where 修复分页列表状态过滤失效;保存并返回 ConsigneeCountryName;预报成功后异步 UpsertSmallBag;仅草稿/已预报可删除。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
095ad66717
commit
fe30d071e6
1
db.sql
1
db.sql
@ -59,6 +59,7 @@ CREATE TABLE `t_order` (
|
|||||||
`ProductCode` VARCHAR(128) NULL COMMENT '产品编码',
|
`ProductCode` VARCHAR(128) NULL COMMENT '产品编码',
|
||||||
`ChannelCode` VARCHAR(128) NULL COMMENT '渠道编码',
|
`ChannelCode` VARCHAR(128) NULL COMMENT '渠道编码',
|
||||||
`ConsigneeCountry` VARCHAR(8) NULL COMMENT '国家二字码',
|
`ConsigneeCountry` VARCHAR(8) NULL COMMENT '国家二字码',
|
||||||
|
`ConsigneeCountryName` VARCHAR(64) NULL COMMENT '国家中文名称',
|
||||||
`CargoAttribute` VARCHAR(32) NULL COMMENT '货物属性:general/electronics/sensitive',
|
`CargoAttribute` VARCHAR(32) NULL COMMENT '货物属性:general/electronics/sensitive',
|
||||||
`ExportCustomsMode` VARCHAR(32) NULL COMMENT '出口报关方式',
|
`ExportCustomsMode` VARCHAR(32) NULL COMMENT '出口报关方式',
|
||||||
`PredeclareFailReason` VARCHAR(512) NULL COMMENT '预报失败原因',
|
`PredeclareFailReason` VARCHAR(512) NULL COMMENT '预报失败原因',
|
||||||
|
|||||||
9
sql/migrate_t_order_consignee_country_name.sql
Normal file
9
sql/migrate_t_order_consignee_country_name.sql
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
-- t_order 增加收件国家中文名称
|
||||||
|
ALTER TABLE `t_order`
|
||||||
|
ADD COLUMN `ConsigneeCountryName` VARCHAR(64) NULL COMMENT '国家中文名称' AFTER `ConsigneeCountry`;
|
||||||
|
|
||||||
|
-- 历史数据:按二字码回填中文名
|
||||||
|
UPDATE `t_order` o
|
||||||
|
INNER JOIN `t_country` c ON c.`CountryCode` = o.`ConsigneeCountry`
|
||||||
|
SET o.`ConsigneeCountryName` = c.`ChineseName`
|
||||||
|
WHERE o.`ConsigneeCountryName` IS NULL OR o.`ConsigneeCountryName` = '';
|
||||||
@ -45,6 +45,16 @@ public class OrderOutput
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string? ChannelName { get; set; }
|
public string? ChannelName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 收件国家/地区二字码。
|
||||||
|
/// </summary>
|
||||||
|
public string? ConsigneeCountry { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 收件国家/地区中文名称。
|
||||||
|
/// </summary>
|
||||||
|
public string? ConsigneeCountryName { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 货物属性。
|
/// 货物属性。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -36,10 +36,15 @@ public class OrderPaginatedOutput
|
|||||||
public string? ChannelCode { get; set; }
|
public string? ChannelCode { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 收件国家/地区。
|
/// 收件国家/地区二字码。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string? ConsigneeCountry { get; set; }
|
public string? ConsigneeCountry { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 收件国家/地区中文名称。
|
||||||
|
/// </summary>
|
||||||
|
public string? ConsigneeCountryName { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 货物属性。
|
/// 货物属性。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -24,4 +24,14 @@ public class OrderParcelCreateOutput
|
|||||||
/// 面单地址。
|
/// 面单地址。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string LabelUrl { get; set; } = string.Empty;
|
public string LabelUrl { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 收件国家/地区二字码。
|
||||||
|
/// </summary>
|
||||||
|
public string? ConsigneeCountry { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 收件国家/地区中文名称。
|
||||||
|
/// </summary>
|
||||||
|
public string? ConsigneeCountryName { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,10 +22,10 @@ public class KpiAreaSnapshotRuleDto
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string? PostcodeRange { get; set; }
|
public string? PostcodeRange { get; set; }
|
||||||
|
|
||||||
/// <summary>旧快照兼容:反序列化后由 <see cref="PublishSnapshotNormalizer"/> 转为 PostcodeRange 并清空。</summary>
|
/// <summary>旧快照兼容:反序列化后由 <see cref="Oms.Application.Shared.PublishSnapshotNormalizer"/> 转为 PostcodeRange 并清空。</summary>
|
||||||
public string? LeftPostcode { get; set; }
|
public string? LeftPostcode { get; set; }
|
||||||
|
|
||||||
/// <summary>旧快照兼容:反序列化后由 <see cref="PublishSnapshotNormalizer"/> 转为 PostcodeRange 并清空。</summary>
|
/// <summary>旧快照兼容:反序列化后由 <see cref="Oms.Application.Shared.PublishSnapshotNormalizer"/> 转为 PostcodeRange 并清空。</summary>
|
||||||
public string? RightPostcode { get; set; }
|
public string? RightPostcode { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,11 +56,11 @@ public class WeightRoundingSnapshotRangeDto
|
|||||||
|
|
||||||
public string? Public { get; set; }
|
public string? Public { get; set; }
|
||||||
|
|
||||||
/// <summary>旧快照/明细 API 兼容(t_weight_rounding_info.StartValue);读取后由 <see cref="PublishSnapshotNormalizer"/> 合并至 <see cref="Start"/>。</summary>
|
/// <summary>旧快照/明细 API 兼容(t_weight_rounding_info.StartValue);读取后由 <see cref="Oms.Application.Shared.PublishSnapshotNormalizer"/> 合并至 <see cref="Start"/>。</summary>
|
||||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
|
||||||
public decimal StartValue { get; set; }
|
public decimal StartValue { get; set; }
|
||||||
|
|
||||||
/// <summary>旧快照/明细 API 兼容(t_weight_rounding_info.EndValue);读取后由 <see cref="PublishSnapshotNormalizer"/> 合并至 <see cref="End"/>。</summary>
|
/// <summary>旧快照/明细 API 兼容(t_weight_rounding_info.EndValue);读取后由 <see cref="Oms.Application.Shared.PublishSnapshotNormalizer"/> 合并至 <see cref="End"/>。</summary>
|
||||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
|
||||||
public decimal EndValue { get; set; }
|
public decimal EndValue { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,6 @@
|
|||||||
|
namespace Oms.Application.Integrations.SmallBagTrack;
|
||||||
|
|
||||||
|
public interface ISmallBagTrackOrderClient
|
||||||
|
{
|
||||||
|
Task UpsertAsync(SmallBagTrackUpsertRequest request, CancellationToken cancellationToken = default);
|
||||||
|
}
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
namespace Oms.Application.Integrations.SmallBagTrack;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 小包预报成功后异步通知轨迹服务。
|
||||||
|
/// </summary>
|
||||||
|
public interface ISmallBagTrackOrderNotifier
|
||||||
|
{
|
||||||
|
void Enqueue(SmallBagTrackUpsertRequest request);
|
||||||
|
}
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
namespace Oms.Application.Integrations.SmallBagTrack;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 小包轨迹订单服务配置(Consul: Oms:TrackService)。
|
||||||
|
/// </summary>
|
||||||
|
public class SmallBagTrackOptions
|
||||||
|
{
|
||||||
|
public const string SectionName = "Oms:TrackService";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否启用异步同步。
|
||||||
|
/// </summary>
|
||||||
|
public bool Enabled { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 轨迹服务根地址,如 https://track.example.com。
|
||||||
|
/// </summary>
|
||||||
|
public string BaseUrl { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// HTTP 超时(秒)。
|
||||||
|
/// </summary>
|
||||||
|
public int TimeoutSeconds { get; set; } = 30;
|
||||||
|
}
|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
using System.Net.Http.Json;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
namespace Oms.Application.Integrations.SmallBagTrack;
|
||||||
|
|
||||||
|
internal sealed class SmallBagTrackOrderClient(
|
||||||
|
IHttpClientFactory httpClientFactory,
|
||||||
|
IOptions<SmallBagTrackOptions> options,
|
||||||
|
ILogger<SmallBagTrackOrderClient> logger) : ISmallBagTrackOrderClient
|
||||||
|
{
|
||||||
|
public const string HttpClientName = nameof(SmallBagTrackOrderClient);
|
||||||
|
|
||||||
|
public async Task UpsertAsync(SmallBagTrackUpsertRequest request, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var settings = options.Value;
|
||||||
|
if (!settings.Enabled || string.IsNullOrWhiteSpace(settings.BaseUrl))
|
||||||
|
{
|
||||||
|
logger.LogDebug("UpsertSmallBag skipped: TrackService disabled or BaseUrl empty");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var url = $"{settings.BaseUrl.Trim().TrimEnd('/')}/OrderV2/UpsertSmallBag";
|
||||||
|
var client = httpClientFactory.CreateClient(HttpClientName);
|
||||||
|
client.Timeout = TimeSpan.FromSeconds(Math.Max(5, settings.TimeoutSeconds));
|
||||||
|
|
||||||
|
using var response = await client.PostAsJsonAsync(url, request, cancellationToken);
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var body = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||||
|
logger.LogWarning(
|
||||||
|
"UpsertSmallBag failed for order {OrderNo}: {StatusCode} {Body}",
|
||||||
|
request.OrderNo,
|
||||||
|
(int)response.StatusCode,
|
||||||
|
body);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.LogInformation("UpsertSmallBag succeeded for order {OrderNo}", request.OrderNo);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace Oms.Application.Integrations.SmallBagTrack;
|
||||||
|
|
||||||
|
internal sealed class SmallBagTrackOrderNotifier(
|
||||||
|
IServiceScopeFactory scopeFactory,
|
||||||
|
ILogger<SmallBagTrackOrderNotifier> logger) : ISmallBagTrackOrderNotifier
|
||||||
|
{
|
||||||
|
public void Enqueue(SmallBagTrackUpsertRequest request)
|
||||||
|
{
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var scope = scopeFactory.CreateScope();
|
||||||
|
var client = scope.ServiceProvider.GetRequiredService<ISmallBagTrackOrderClient>();
|
||||||
|
await client.UpsertAsync(request);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
logger.LogWarning(ex, "Async UpsertSmallBag failed for order {OrderNo}", request.OrderNo);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace Oms.Application.Integrations.SmallBagTrack;
|
||||||
|
|
||||||
|
public static class SmallBagTrackServiceExtensions
|
||||||
|
{
|
||||||
|
public static IServiceCollection AddOmsSmallBagTrack(this IServiceCollection services, IConfiguration configuration)
|
||||||
|
{
|
||||||
|
services.Configure<SmallBagTrackOptions>(configuration.GetSection(SmallBagTrackOptions.SectionName));
|
||||||
|
services.AddHttpClient(SmallBagTrackOrderClient.HttpClientName);
|
||||||
|
services.AddSingleton<ISmallBagTrackOrderClient, SmallBagTrackOrderClient>();
|
||||||
|
services.AddSingleton<ISmallBagTrackOrderNotifier, SmallBagTrackOrderNotifier>();
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
namespace Oms.Application.Integrations.SmallBagTrack;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// POST /OrderV2/UpsertSmallBag 请求体。
|
||||||
|
/// </summary>
|
||||||
|
public class SmallBagTrackUpsertRequest
|
||||||
|
{
|
||||||
|
public string Client { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string OrderNo { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string TrackNo { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string OtherNo { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string Channel { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string Product { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string ApiCode { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public SmallBagTrackConsigneeRequest Consignee { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SmallBagTrackConsigneeRequest
|
||||||
|
{
|
||||||
|
public string CountryCode { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string Province { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string Postcode { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string City { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string Email { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
@ -5,6 +5,8 @@
|
|||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||||
|
<!-- 已写 XML 注释的 API 仍输出到文档;未补全注释的公共成员不阻塞编译 -->
|
||||||
|
<NoWarn>$(NoWarn);CS1591</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
using Fake.Modularity;
|
using Fake.Modularity;
|
||||||
using Fake.ObjectMapping.AutoMapper;
|
using Fake.ObjectMapping.AutoMapper;
|
||||||
using Fake.Security.Claims;
|
using Fake.Security.Claims;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Oms.Application.Integrations.SmallBagTrack;
|
||||||
using Oms.Domain;
|
using Oms.Domain;
|
||||||
|
|
||||||
namespace Oms.Application;
|
namespace Oms.Application;
|
||||||
@ -21,5 +23,6 @@ public class OmsApplicationModule : FakeModule
|
|||||||
{
|
{
|
||||||
options.ScanProfiles<OmsApplicationModule>();
|
options.ScanProfiles<OmsApplicationModule>();
|
||||||
});
|
});
|
||||||
|
context.Services.AddOmsSmallBagTrack(context.Services.GetConfiguration());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -275,6 +275,11 @@ public partial class SalesProductService
|
|||||||
|
|
||||||
// 规则回滚既恢复产品配置,也恢复排货服务商列表。
|
// 规则回滚既恢复产品配置,也恢复排货服务商列表。
|
||||||
var product = await GetProductOrThrowAsync(input.SalesProductId);
|
var product = await GetProductOrThrowAsync(input.SalesProductId);
|
||||||
|
if (rulesSnapshot.Product == null)
|
||||||
|
{
|
||||||
|
throw new Fake.Application.BusinessException("发布快照缺少产品配置");
|
||||||
|
}
|
||||||
|
|
||||||
ApplyProductInput(product, rulesSnapshot.Product);
|
ApplyProductInput(product, rulesSnapshot.Product);
|
||||||
await SalesProductRepository.UpdateAsync(product);
|
await SalesProductRepository.UpdateAsync(product);
|
||||||
|
|
||||||
|
|||||||
@ -9,6 +9,7 @@ using Oms.Application.Dtos.Shared;
|
|||||||
using Oms.Application.Shared;
|
using Oms.Application.Shared;
|
||||||
using Oms.Domain.OrdersAggregate.Consts;
|
using Oms.Domain.OrdersAggregate.Consts;
|
||||||
using Oms.Domain.OrdersAggregate.Entities;
|
using Oms.Domain.OrdersAggregate.Entities;
|
||||||
|
using Oms.Domain.SystemAggregate.Entities;
|
||||||
|
|
||||||
namespace Oms.Application.Services.Orders;
|
namespace Oms.Application.Services.Orders;
|
||||||
|
|
||||||
@ -58,6 +59,16 @@ public partial class OrderService
|
|||||||
var productMap = products.ToDictionary(x => x.ProductCode, StringComparer.OrdinalIgnoreCase);
|
var productMap = products.ToDictionary(x => x.ProductCode, StringComparer.OrdinalIgnoreCase);
|
||||||
var channelMap = channels.ToDictionary(x => x.ChannelCode, StringComparer.OrdinalIgnoreCase);
|
var channelMap = channels.ToDictionary(x => x.ChannelCode, StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
var countryCodes = orders
|
||||||
|
.Where(x => !string.IsNullOrWhiteSpace(x.ConsigneeCountry))
|
||||||
|
.Select(x => x.ConsigneeCountry!.Trim())
|
||||||
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||||
|
.ToArray();
|
||||||
|
var countries = countryCodes.Length == 0
|
||||||
|
? []
|
||||||
|
: await CountryRepository.GetListAsync(x => countryCodes.Contains(x.CountryCode));
|
||||||
|
var countryMap = countries.ToDictionary(x => x.CountryCode, StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
return orders.Select(order =>
|
return orders.Select(order =>
|
||||||
{
|
{
|
||||||
senderMap.TryGetValue(order.Id, out var sender);
|
senderMap.TryGetValue(order.Id, out var sender);
|
||||||
@ -86,6 +97,8 @@ public partial class OrderService
|
|||||||
&& channelMap.TryGetValue(order.ChannelCode.Trim(), out var channel)
|
&& channelMap.TryGetValue(order.ChannelCode.Trim(), out var channel)
|
||||||
? channel.ChannelName
|
? channel.ChannelName
|
||||||
: null,
|
: null,
|
||||||
|
ConsigneeCountry = order.ConsigneeCountry,
|
||||||
|
ConsigneeCountryName = ResolveConsigneeCountryName(order, countryMap),
|
||||||
CargoAttribute = order.CargoAttribute,
|
CargoAttribute = order.CargoAttribute,
|
||||||
WarehouseId = order.WarehouseId,
|
WarehouseId = order.WarehouseId,
|
||||||
Warehouse = order.WarehouseId.HasValue && warehouseMap.TryGetValue(order.WarehouseId.Value, out var warehouse)
|
Warehouse = order.WarehouseId.HasValue && warehouseMap.TryGetValue(order.WarehouseId.Value, out var warehouse)
|
||||||
@ -488,6 +501,25 @@ public partial class OrderService
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string? ResolveConsigneeCountryName(
|
||||||
|
Order order,
|
||||||
|
IReadOnlyDictionary<string, Country> countryMap)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(order.ConsigneeCountryName))
|
||||||
|
{
|
||||||
|
return order.ConsigneeCountryName.Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(order.ConsigneeCountry))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return countryMap.TryGetValue(order.ConsigneeCountry.Trim(), out var country)
|
||||||
|
? country.ChineseName
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
private static OrderSenderOutput? MapSender(OrderSender? sender)
|
private static OrderSenderOutput? MapSender(OrderSender? sender)
|
||||||
{
|
{
|
||||||
return sender == null
|
return sender == null
|
||||||
|
|||||||
@ -615,6 +615,7 @@ public partial class OrderService
|
|||||||
var country = await GetCountryOrThrowAsync(input.CountryCode, "收件国家不存在");
|
var country = await GetCountryOrThrowAsync(input.CountryCode, "收件国家不存在");
|
||||||
recipient.Country = country.Id;
|
recipient.Country = country.Id;
|
||||||
order.ConsigneeCountry = country.CountryCode;
|
order.ConsigneeCountry = country.CountryCode;
|
||||||
|
order.ConsigneeCountryName = country.ChineseName;
|
||||||
await OrderRepository.UpdateAsync(order);
|
await OrderRepository.UpdateAsync(order);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
using Fake.Application;
|
using Fake.Application;
|
||||||
using Oms.Application.Dtos.Channels;
|
using Oms.Application.Dtos.Channels;
|
||||||
using Oms.Application.Dtos.Orders;
|
using Oms.Application.Dtos.Orders;
|
||||||
|
using Oms.Application.Integrations.SmallBagTrack;
|
||||||
using Oms.Application.Shared;
|
using Oms.Application.Shared;
|
||||||
using Oms.Domain.ChannelsAggregate.Consts;
|
using Oms.Domain.ChannelsAggregate.Consts;
|
||||||
using Oms.Domain.ChannelsAggregate.Entities;
|
using Oms.Domain.ChannelsAggregate.Entities;
|
||||||
@ -68,7 +69,7 @@ public partial class OrderService
|
|||||||
}
|
}
|
||||||
|
|
||||||
var serviceNo = await ResolveServiceNoAsync(input.ServiceNo);
|
var serviceNo = await ResolveServiceNoAsync(input.ServiceNo);
|
||||||
var order = BuildDraftOrderEntity(input, product, country.CountryCode, snapshotBinding, serviceNo);
|
var order = BuildDraftOrderEntity(input, product, country.CountryCode, country.ChineseName, snapshotBinding, serviceNo);
|
||||||
order = await OrderRepository.InsertAsync(order);
|
order = await OrderRepository.InsertAsync(order);
|
||||||
|
|
||||||
var weights = BuildInitialWeightRows(order.Id, input);
|
var weights = BuildInitialWeightRows(order.Id, input);
|
||||||
@ -128,6 +129,8 @@ public partial class OrderService
|
|||||||
order.UpdateTime = DateTime.Now;
|
order.UpdateTime = DateTime.Now;
|
||||||
await OrderRepository.UpdateAsync(order);
|
await OrderRepository.UpdateAsync(order);
|
||||||
|
|
||||||
|
await EnqueueSmallBagTrackUpsertAsync(order, customer, input.Receiver);
|
||||||
|
|
||||||
return await BuildParcelCreateOutputAsync(order);
|
return await BuildParcelCreateOutputAsync(order);
|
||||||
}
|
}
|
||||||
catch (BusinessException ex)
|
catch (BusinessException ex)
|
||||||
@ -1012,6 +1015,7 @@ public partial class OrderService
|
|||||||
OrderParcelCreateInput input,
|
OrderParcelCreateInput input,
|
||||||
SalesProduct product,
|
SalesProduct product,
|
||||||
string consigneeCountry,
|
string consigneeCountry,
|
||||||
|
string consigneeCountryName,
|
||||||
ProductSnapshotBinding snapshotBinding,
|
ProductSnapshotBinding snapshotBinding,
|
||||||
string serviceNo)
|
string serviceNo)
|
||||||
{
|
{
|
||||||
@ -1024,6 +1028,7 @@ public partial class OrderService
|
|||||||
ProductCode = input.ProductCode,
|
ProductCode = input.ProductCode,
|
||||||
ChannelCode = input.ChannelCode ?? product.ProductCode,
|
ChannelCode = input.ChannelCode ?? product.ProductCode,
|
||||||
ConsigneeCountry = consigneeCountry,
|
ConsigneeCountry = consigneeCountry,
|
||||||
|
ConsigneeCountryName = consigneeCountryName,
|
||||||
CargoAttribute = input.CargoAttribute ?? CargoAttributeCodes.General,
|
CargoAttribute = input.CargoAttribute ?? CargoAttributeCodes.General,
|
||||||
ExportCustomsMode = input.ExportCustomsMode,
|
ExportCustomsMode = input.ExportCustomsMode,
|
||||||
WarehouseId = input.WarehouseId,
|
WarehouseId = input.WarehouseId,
|
||||||
@ -1401,12 +1406,54 @@ public partial class OrderService
|
|||||||
ClientNo = order.ClientNo,
|
ClientNo = order.ClientNo,
|
||||||
ServiceNo = order.ServiceNo,
|
ServiceNo = order.ServiceNo,
|
||||||
TrackNo = order.TrackNo,
|
TrackNo = order.TrackNo,
|
||||||
|
ConsigneeCountry = order.ConsigneeCountry,
|
||||||
|
ConsigneeCountryName = order.ConsigneeCountryName,
|
||||||
LabelUrl = order.OrderStatus == OrderStatusCodes.Predeclared
|
LabelUrl = order.OrderStatus == OrderStatusCodes.Predeclared
|
||||||
? await CreateOrderPrintFileAsync(mapped)
|
? await CreateOrderPrintFileAsync(mapped)
|
||||||
: string.Empty
|
: string.Empty
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ISmallBagTrackOrderNotifier SmallBagTrackOrderNotifier =>
|
||||||
|
LazyServiceProvider.GetRequiredService<ISmallBagTrackOrderNotifier>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 小包预报成功后异步同步轨迹订单(POST /OrderV2/UpsertSmallBag)。
|
||||||
|
/// </summary>
|
||||||
|
private async Task EnqueueSmallBagTrackUpsertAsync(
|
||||||
|
Order order,
|
||||||
|
Customer? customer,
|
||||||
|
OrderParcelReceiverInput? receiver)
|
||||||
|
{
|
||||||
|
var apiCode = string.Empty;
|
||||||
|
if (!string.IsNullOrWhiteSpace(order.ChannelCode))
|
||||||
|
{
|
||||||
|
var channel = await ServiceChannelRepository.GetByChannelCodeAsync(order.ChannelCode.Trim());
|
||||||
|
apiCode = channel?.ForecastCode?.Trim() ?? string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
var request = new SmallBagTrackUpsertRequest
|
||||||
|
{
|
||||||
|
Client = customer?.CustomerCode?.Trim() ?? string.Empty,
|
||||||
|
OrderNo = order.ServiceNo,
|
||||||
|
TrackNo = order.TrackNo?.Trim() ?? string.Empty,
|
||||||
|
OtherNo = order.ClientNo?.Trim() ?? string.Empty,
|
||||||
|
Channel = order.ChannelCode?.Trim() ?? string.Empty,
|
||||||
|
Product = order.ProductCode?.Trim() ?? string.Empty,
|
||||||
|
ApiCode = apiCode,
|
||||||
|
Consignee = new SmallBagTrackConsigneeRequest
|
||||||
|
{
|
||||||
|
CountryCode = receiver?.CountryCode?.Trim() ?? order.ConsigneeCountry?.Trim() ?? string.Empty,
|
||||||
|
Province = receiver?.Province?.Trim() ?? string.Empty,
|
||||||
|
Postcode = receiver?.ZipCode?.Trim() ?? string.Empty,
|
||||||
|
City = receiver?.City?.Trim() ?? string.Empty,
|
||||||
|
Email = receiver?.Email?.Trim() ?? string.Empty
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
SmallBagTrackOrderNotifier.Enqueue(request);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 从会话解析有效客户(客户登录自动补全;管理员须传 customerId)。
|
/// 从会话解析有效客户(客户登录自动补全;管理员须传 customerId)。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
using System.Linq.Expressions;
|
|
||||||
using Fake.Application;
|
using Fake.Application;
|
||||||
using Fake.Domain.Exceptions;
|
using Fake.Domain.Exceptions;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
@ -9,6 +8,7 @@ using Oms.Application.Dtos.Shared;
|
|||||||
using Oms.Application.Shared;
|
using Oms.Application.Shared;
|
||||||
using Oms.Domain.ChannelsAggregate.Repositories;
|
using Oms.Domain.ChannelsAggregate.Repositories;
|
||||||
using Oms.Domain.CustomersAggregate.Repositories;
|
using Oms.Domain.CustomersAggregate.Repositories;
|
||||||
|
using Oms.Domain.OrdersAggregate;
|
||||||
using Oms.Domain.OrdersAggregate.Consts;
|
using Oms.Domain.OrdersAggregate.Consts;
|
||||||
using Oms.Domain.OrdersAggregate.Entities;
|
using Oms.Domain.OrdersAggregate.Entities;
|
||||||
using Oms.Domain.OrdersAggregate.Repositories;
|
using Oms.Domain.OrdersAggregate.Repositories;
|
||||||
@ -47,15 +47,13 @@ public partial class OrderService : ApplicationService
|
|||||||
CustomerRepository,
|
CustomerRepository,
|
||||||
CurrentUser,
|
CurrentUser,
|
||||||
null);
|
null);
|
||||||
var predicate = BuildPaginatedPredicate(input, customerId);
|
var filter = BuildListFilter(input, customerId);
|
||||||
var pageIndex = OmsDocHelper.NormalizePage(input.Page);
|
var pageIndex = OmsDocHelper.NormalizePage(input.Page);
|
||||||
var pageSize = OmsDocHelper.NormalizePageSize(input.PageSize);
|
var pageSize = OmsDocHelper.NormalizePageSize(input.PageSize);
|
||||||
var orders = await OrderRepository.GetPagedListAsync(
|
var (orders, total) = await OrderRepository.GetFilteredPagedListAsync(
|
||||||
predicate,
|
|
||||||
pageIndex,
|
pageIndex,
|
||||||
pageSize,
|
pageSize,
|
||||||
new Dictionary<string, bool> { [nameof(Order.UpdateTime)] = false, [nameof(Order.Id)] = false });
|
filter);
|
||||||
var total = await OrderRepository.CountAsync(predicate);
|
|
||||||
return new OmsPageResult<OrderOutput> { Items = await MapOrderOutputsAsync(orders), Total = total };
|
return new OmsPageResult<OrderOutput> { Items = await MapOrderOutputsAsync(orders), Total = total };
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,6 +228,11 @@ public partial class OrderService : ApplicationService
|
|||||||
public async Task DeleteAsync(OrderActionInput input)
|
public async Task DeleteAsync(OrderActionInput input)
|
||||||
{
|
{
|
||||||
var order = await GetOrderOrThrowAsync(input.Id);
|
var order = await GetOrderOrThrowAsync(input.Id);
|
||||||
|
if (order.OrderStatus is not (OrderStatusCodes.Draft or OrderStatusCodes.Predeclared))
|
||||||
|
{
|
||||||
|
throw new BusinessException("仅草稿或已预报订单可删除");
|
||||||
|
}
|
||||||
|
|
||||||
order.OrderStatus = OrderStatusCodes.Deleted;
|
order.OrderStatus = OrderStatusCodes.Deleted;
|
||||||
await OrderRepository.UpdateAsync(order);
|
await OrderRepository.UpdateAsync(order);
|
||||||
await OrderRepository.DeleteAsync(order);
|
await OrderRepository.DeleteAsync(order);
|
||||||
@ -273,9 +276,7 @@ public partial class OrderService : ApplicationService
|
|||||||
CustomerRepository,
|
CustomerRepository,
|
||||||
CurrentUser,
|
CurrentUser,
|
||||||
null);
|
null);
|
||||||
var orders = await OrderRepository.GetListAsync(
|
var orders = await OrderRepository.GetFilteredListAsync(BuildListFilter(input, customerId));
|
||||||
BuildExportPredicate(input, customerId),
|
|
||||||
new Dictionary<string, bool> { [nameof(Order.CreateTime)] = false, [nameof(Order.Id)] = false });
|
|
||||||
var fileName = $"orders-{DateTime.Now:yyyyMMddHHmmss}.xlsx";
|
var fileName = $"orders-{DateTime.Now:yyyyMMddHHmmss}.xlsx";
|
||||||
var directory = GetOrderExportDirectory();
|
var directory = GetOrderExportDirectory();
|
||||||
Directory.CreateDirectory(directory);
|
Directory.CreateDirectory(directory);
|
||||||
@ -478,170 +479,25 @@ public partial class OrderService : ApplicationService
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
private static OrderListFilter BuildListFilter(OrderPaginatedInput input, int? customerId) =>
|
||||||
/// 将订单列表请求对象转换为仓储可执行的查询表达式。
|
new()
|
||||||
/// </summary>
|
|
||||||
/// <param name="input">入参:分页查询条件对象。</param>
|
|
||||||
/// <param name="customerId">入参:会话客户 ID,空值表示不按客户隔离。</param>
|
|
||||||
/// <returns>出参:订单查询表达式。</returns>
|
|
||||||
private Expression<Func<Order, bool>> BuildPaginatedPredicate(OrderPaginatedInput input, int? customerId)
|
|
||||||
{
|
{
|
||||||
return BuildOrderPredicate(
|
Status = input.Status,
|
||||||
input.Status,
|
Keyword = input.Keyword,
|
||||||
input.Keyword,
|
CreateTimeStart = input.CreateTimeStart,
|
||||||
input.CreateTimeStart,
|
CreateTimeEnd = input.CreateTimeEnd,
|
||||||
input.CreateTimeEnd,
|
CustomerId = customerId
|
||||||
customerId);
|
};
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
private static OrderListFilter BuildListFilter(OrderExportInput input, int? customerId) =>
|
||||||
/// 将订单导出请求对象转换为仓储可执行的查询表达式。
|
new()
|
||||||
/// </summary>
|
|
||||||
/// <param name="input">入参:导出筛选条件对象。</param>
|
|
||||||
/// <param name="customerId">入参:会话客户 ID,空值表示不按客户隔离。</param>
|
|
||||||
/// <returns>出参:订单查询表达式。</returns>
|
|
||||||
private Expression<Func<Order, bool>> BuildExportPredicate(OrderExportInput input, int? customerId)
|
|
||||||
{
|
{
|
||||||
return BuildOrderPredicate(
|
Status = input.Status,
|
||||||
input.Status,
|
Keyword = input.Keyword,
|
||||||
input.Keyword,
|
CreateTimeStart = input.CreateTimeStart,
|
||||||
input.CreateTimeStart,
|
CreateTimeEnd = input.CreateTimeEnd,
|
||||||
input.CreateTimeEnd,
|
CustomerId = customerId
|
||||||
customerId);
|
};
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 统一构造订单查询条件。
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="status">入参:订单状态,传入 <c>all</c> 或空值时表示不按状态过滤。</param>
|
|
||||||
/// <param name="keyword">入参:关键词,可匹配运单号、客户单号、跟踪号。</param>
|
|
||||||
/// <param name="createTimeStart">入参:创建时间开始值。</param>
|
|
||||||
/// <param name="createTimeEnd">入参:创建时间结束值。</param>
|
|
||||||
/// <param name="customerId">入参:客户主键,空值表示不按客户隔离。</param>
|
|
||||||
/// <returns>出参:可直接交给 SqlSugar 仓储执行的订单筛选表达式。</returns>
|
|
||||||
/// <remarks>
|
|
||||||
/// 核心业务逻辑:
|
|
||||||
/// 1. 先把状态和关键词做统一清洗;
|
|
||||||
/// 2. 再按时间区间是否存在拆分成不同表达式;
|
|
||||||
/// 3. 避免把 <see cref="Nullable{T}.HasValue"/> / <see cref="Nullable{T}.Value"/> 放进可翻译 SQL 的表达式中,
|
|
||||||
/// 从而规避 MySQL 下时间条件被错误翻译的问题;
|
|
||||||
/// 4. 状态筛选同样显式分支,避免 <c>statusValue == null || order.OrderStatus == statusValue</c> 被 SqlSugar 误译。
|
|
||||||
/// </remarks>
|
|
||||||
private static Expression<Func<Order, bool>> BuildOrderPredicate(
|
|
||||||
string? status,
|
|
||||||
string? keyword,
|
|
||||||
DateTime? createTimeStart,
|
|
||||||
DateTime? createTimeEnd,
|
|
||||||
int? customerId = null)
|
|
||||||
{
|
|
||||||
status = status?.Trim();
|
|
||||||
keyword = keyword?.Trim();
|
|
||||||
var orderStatusFilter = !string.IsNullOrWhiteSpace(status) && !string.Equals(status, "all", StringComparison.OrdinalIgnoreCase)
|
|
||||||
? status
|
|
||||||
: null;
|
|
||||||
var hasStatusFilter = orderStatusFilter != null;
|
|
||||||
var hasKeywordFilter = !string.IsNullOrWhiteSpace(keyword);
|
|
||||||
var hasCustomerFilter = customerId.HasValue;
|
|
||||||
var customerIdValue = customerId.GetValueOrDefault();
|
|
||||||
|
|
||||||
// 状态与时间均显式分支,避免 SqlSugar 翻译「闭包变量 == null || 字段 == 变量」时丢失条件。
|
|
||||||
if (createTimeStart.HasValue && createTimeEnd.HasValue)
|
|
||||||
{
|
|
||||||
var start = createTimeStart.Value;
|
|
||||||
var end = createTimeEnd.Value;
|
|
||||||
if (hasStatusFilter)
|
|
||||||
{
|
|
||||||
var statusFilter = orderStatusFilter!;
|
|
||||||
return order =>
|
|
||||||
(!hasCustomerFilter || order.CustomerId == customerIdValue) &&
|
|
||||||
order.OrderStatus == statusFilter &&
|
|
||||||
(!hasKeywordFilter ||
|
|
||||||
order.ServiceNo.Contains(keyword!) ||
|
|
||||||
(order.ClientNo ?? string.Empty).Contains(keyword!) ||
|
|
||||||
(order.TrackNo ?? string.Empty).Contains(keyword!)) &&
|
|
||||||
order.CreateTime >= start &&
|
|
||||||
order.CreateTime <= end;
|
|
||||||
}
|
|
||||||
|
|
||||||
return order =>
|
|
||||||
(!hasCustomerFilter || order.CustomerId == customerIdValue) &&
|
|
||||||
(!hasKeywordFilter ||
|
|
||||||
order.ServiceNo.Contains(keyword!) ||
|
|
||||||
(order.ClientNo ?? string.Empty).Contains(keyword!) ||
|
|
||||||
(order.TrackNo ?? string.Empty).Contains(keyword!)) &&
|
|
||||||
order.CreateTime >= start &&
|
|
||||||
order.CreateTime <= end;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (createTimeStart.HasValue)
|
|
||||||
{
|
|
||||||
var start = createTimeStart.Value;
|
|
||||||
if (hasStatusFilter)
|
|
||||||
{
|
|
||||||
var statusFilter = orderStatusFilter!;
|
|
||||||
return order =>
|
|
||||||
(!hasCustomerFilter || order.CustomerId == customerIdValue) &&
|
|
||||||
order.OrderStatus == statusFilter &&
|
|
||||||
(!hasKeywordFilter ||
|
|
||||||
order.ServiceNo.Contains(keyword!) ||
|
|
||||||
(order.ClientNo ?? string.Empty).Contains(keyword!) ||
|
|
||||||
(order.TrackNo ?? string.Empty).Contains(keyword!)) &&
|
|
||||||
order.CreateTime >= start;
|
|
||||||
}
|
|
||||||
|
|
||||||
return order =>
|
|
||||||
(!hasCustomerFilter || order.CustomerId == customerIdValue) &&
|
|
||||||
(!hasKeywordFilter ||
|
|
||||||
order.ServiceNo.Contains(keyword!) ||
|
|
||||||
(order.ClientNo ?? string.Empty).Contains(keyword!) ||
|
|
||||||
(order.TrackNo ?? string.Empty).Contains(keyword!)) &&
|
|
||||||
order.CreateTime >= start;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (createTimeEnd.HasValue)
|
|
||||||
{
|
|
||||||
var end = createTimeEnd.Value;
|
|
||||||
if (hasStatusFilter)
|
|
||||||
{
|
|
||||||
var statusFilter = orderStatusFilter!;
|
|
||||||
return order =>
|
|
||||||
(!hasCustomerFilter || order.CustomerId == customerIdValue) &&
|
|
||||||
order.OrderStatus == statusFilter &&
|
|
||||||
(!hasKeywordFilter ||
|
|
||||||
order.ServiceNo.Contains(keyword!) ||
|
|
||||||
(order.ClientNo ?? string.Empty).Contains(keyword!) ||
|
|
||||||
(order.TrackNo ?? string.Empty).Contains(keyword!)) &&
|
|
||||||
order.CreateTime <= end;
|
|
||||||
}
|
|
||||||
|
|
||||||
return order =>
|
|
||||||
(!hasCustomerFilter || order.CustomerId == customerIdValue) &&
|
|
||||||
(!hasKeywordFilter ||
|
|
||||||
order.ServiceNo.Contains(keyword!) ||
|
|
||||||
(order.ClientNo ?? string.Empty).Contains(keyword!) ||
|
|
||||||
(order.TrackNo ?? string.Empty).Contains(keyword!)) &&
|
|
||||||
order.CreateTime <= end;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasStatusFilter)
|
|
||||||
{
|
|
||||||
var statusFilter = orderStatusFilter!;
|
|
||||||
return order =>
|
|
||||||
(!hasCustomerFilter || order.CustomerId == customerIdValue) &&
|
|
||||||
order.OrderStatus == statusFilter &&
|
|
||||||
(!hasKeywordFilter ||
|
|
||||||
order.ServiceNo.Contains(keyword!) ||
|
|
||||||
(order.ClientNo ?? string.Empty).Contains(keyword!) ||
|
|
||||||
(order.TrackNo ?? string.Empty).Contains(keyword!));
|
|
||||||
}
|
|
||||||
|
|
||||||
return order =>
|
|
||||||
(!hasCustomerFilter || order.CustomerId == customerIdValue) &&
|
|
||||||
(!hasKeywordFilter ||
|
|
||||||
order.ServiceNo.Contains(keyword!) ||
|
|
||||||
(order.ClientNo ?? string.Empty).Contains(keyword!) ||
|
|
||||||
(order.TrackNo ?? string.Empty).Contains(keyword!));
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task EnsureCustomerCanAccessOrderAsync(Order order)
|
private async Task EnsureCustomerCanAccessOrderAsync(Order order)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -20,6 +20,8 @@ public class Order : OmsAuditedAggregateRoot
|
|||||||
|
|
||||||
public string? ConsigneeCountry { get; set; }
|
public string? ConsigneeCountry { get; set; }
|
||||||
|
|
||||||
|
public string? ConsigneeCountryName { get; set; }
|
||||||
|
|
||||||
public string? CargoAttribute { get; set; }
|
public string? CargoAttribute { get; set; }
|
||||||
|
|
||||||
public string? ExportCustomsMode { get; set; }
|
public string? ExportCustomsMode { get; set; }
|
||||||
|
|||||||
17
src/Oms.Domain/OrdersAggregate/OrderListFilter.cs
Normal file
17
src/Oms.Domain/OrdersAggregate/OrderListFilter.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
namespace Oms.Domain.OrdersAggregate;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 订单列表/导出统一筛选条件。
|
||||||
|
/// </summary>
|
||||||
|
public class OrderListFilter
|
||||||
|
{
|
||||||
|
public string? Status { get; set; }
|
||||||
|
|
||||||
|
public string? Keyword { get; set; }
|
||||||
|
|
||||||
|
public DateTime? CreateTimeStart { get; set; }
|
||||||
|
|
||||||
|
public DateTime? CreateTimeEnd { get; set; }
|
||||||
|
|
||||||
|
public int? CustomerId { get; set; }
|
||||||
|
}
|
||||||
@ -5,6 +5,16 @@ namespace Oms.Domain.OrdersAggregate.Repositories;
|
|||||||
|
|
||||||
public interface IOrderRepository : IRepository<Order>
|
public interface IOrderRepository : IRepository<Order>
|
||||||
{
|
{
|
||||||
|
Task<(List<Order> Items, int Total)> GetFilteredPagedListAsync(
|
||||||
|
int pageIndex,
|
||||||
|
int pageSize,
|
||||||
|
OrderListFilter filter,
|
||||||
|
CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
Task<List<Order>> GetFilteredListAsync(
|
||||||
|
OrderListFilter filter,
|
||||||
|
CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
Task<Order?> GetByServiceNoAsync(string serviceNo, CancellationToken cancellationToken = default);
|
Task<Order?> GetByServiceNoAsync(string serviceNo, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
Task<bool> HasServiceNoAsync(string serviceNo, CancellationToken cancellationToken = default);
|
Task<bool> HasServiceNoAsync(string serviceNo, CancellationToken cancellationToken = default);
|
||||||
|
|||||||
@ -10,6 +10,72 @@ namespace Oms.SqlSugarCore.Orders;
|
|||||||
|
|
||||||
public class OrderRepository : OmsSqlSugarRepository<Order>, IOrderRepository, IScopedDependency
|
public class OrderRepository : OmsSqlSugarRepository<Order>, IOrderRepository, IScopedDependency
|
||||||
{
|
{
|
||||||
|
public async Task<(List<Order> Items, int Total)> GetFilteredPagedListAsync(
|
||||||
|
int pageIndex,
|
||||||
|
int pageSize,
|
||||||
|
OrderListFilter filter,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var db = await GetDbContextAsync(cancellationToken);
|
||||||
|
var query = ApplyListFilter(db.Queryable<Order>(), filter);
|
||||||
|
var total = await query.CountAsync(cancellationToken);
|
||||||
|
var items = await query
|
||||||
|
.OrderBy(x => x.UpdateTime, OrderByType.Desc)
|
||||||
|
.OrderBy(x => x.Id, OrderByType.Desc)
|
||||||
|
.ToPageListAsync(pageIndex, pageSize, cancellationToken);
|
||||||
|
return (items, total);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<Order>> GetFilteredListAsync(
|
||||||
|
OrderListFilter filter,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var db = await GetDbContextAsync(cancellationToken);
|
||||||
|
return await ApplyListFilter(db.Queryable<Order>(), filter)
|
||||||
|
.OrderBy(x => x.UpdateTime, OrderByType.Desc)
|
||||||
|
.OrderBy(x => x.Id, OrderByType.Desc)
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ISugarQueryable<Order> ApplyListFilter(ISugarQueryable<Order> query, OrderListFilter filter)
|
||||||
|
{
|
||||||
|
if (filter.CustomerId.HasValue)
|
||||||
|
{
|
||||||
|
var customerId = filter.CustomerId.Value;
|
||||||
|
query = query.Where(x => x.CustomerId == customerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
var status = filter.Status?.Trim();
|
||||||
|
if (!string.IsNullOrWhiteSpace(status)
|
||||||
|
&& !string.Equals(status, "all", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
query = query.Where(x => x.OrderStatus == status);
|
||||||
|
}
|
||||||
|
|
||||||
|
var keyword = filter.Keyword?.Trim();
|
||||||
|
if (!string.IsNullOrWhiteSpace(keyword))
|
||||||
|
{
|
||||||
|
query = query.Where(x =>
|
||||||
|
x.ServiceNo.Contains(keyword)
|
||||||
|
|| (x.ClientNo ?? string.Empty).Contains(keyword)
|
||||||
|
|| (x.TrackNo ?? string.Empty).Contains(keyword));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter.CreateTimeStart.HasValue)
|
||||||
|
{
|
||||||
|
var start = filter.CreateTimeStart.Value;
|
||||||
|
query = query.Where(x => x.CreateTime >= start);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter.CreateTimeEnd.HasValue)
|
||||||
|
{
|
||||||
|
var end = filter.CreateTimeEnd.Value;
|
||||||
|
query = query.Where(x => x.CreateTime <= end);
|
||||||
|
}
|
||||||
|
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
public Task<Order?> GetByServiceNoAsync(string serviceNo, CancellationToken cancellationToken = default)
|
public Task<Order?> GetByServiceNoAsync(string serviceNo, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
return FirstOrDefaultAsync(x => x.ServiceNo == serviceNo, cancellationToken);
|
return FirstOrDefaultAsync(x => x.ServiceNo == serviceNo, cancellationToken);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user