From f063c99d1b52286ff56b0336a979e32dd6d9b783 Mon Sep 17 00:00:00 2001 From: zzh <1368735060@qq.com> Date: Mon, 5 Oct 2026 18:48:21 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20OpenAPI=20=E5=A2=9E=E5=8A=A0=E8=BD=A8?= =?UTF-8?q?=E8=BF=B9/=E6=96=87=E4=BB=B6=E6=9F=A5=E8=AF=A2=EF=BC=8C?= =?UTF-8?q?=E5=88=9B=E5=8D=95=E6=8C=89=20clientNo=20=E5=B9=82=E7=AD=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 客户 HMAC 通过后再查本客户订单;禁止下游面单,附件增加 POD。 Co-authored-by: Cursor --- db.sql | 2 +- .../Dtos/OpenApi/OpenApiQueryDtos.cs | 92 +++++++++ .../ISmallBagTrackOrderClient.cs | 9 + .../SmallBagTrack/SmallBagTrackOrderClient.cs | 174 ++++++++++++++++++ .../CustomerOpenApiCredentialService.cs | 5 +- .../Services/OpenApi/OpenApiService.cs | 139 +++++++++++++- .../Services/Orders/OrderFileManager.cs | 24 ++- .../Services/Orders/OrderService.cs | 40 ++++ .../Shared/OpenApiCustomerOrderResolver.cs | 54 ++++++ .../Consts/OrderFileTypeCodes.cs | 17 +- .../OrdersAggregate/Entities/OrderFile.cs | 2 +- 11 files changed, 550 insertions(+), 8 deletions(-) create mode 100644 src/Oms.Application/Dtos/OpenApi/OpenApiQueryDtos.cs create mode 100644 src/Oms.Application/Shared/OpenApiCustomerOrderResolver.cs diff --git a/db.sql b/db.sql index 009c706..b2c744a 100644 --- a/db.sql +++ b/db.sql @@ -214,7 +214,7 @@ CREATE TABLE `t_order` ( CREATE TABLE `t_order_file` ( `Id` INT NOT NULL AUTO_INCREMENT COMMENT '主键,自增', `OrderId` INT NOT NULL COMMENT '订单Id', - `FileType` VARCHAR(32) NOT NULL COMMENT '附件类型:tradeProof交易证明/systemLabel系统面单/downstreamLabel下游面单/lastMileLabel末端面单/infraredImage红外图片', + `FileType` VARCHAR(32) NOT NULL COMMENT '附件类型:tradeProof交易证明/systemLabel系统面单/downstreamLabel下游面单/lastMileLabel末端面单/infraredImage红外图片/pod签收证明', `FileName` VARCHAR(255) NULL COMMENT '原始文件名', `FileUrl` VARCHAR(512) NOT NULL COMMENT '文件地址(相对路径或外链)', `ContentType` VARCHAR(128) NULL COMMENT '文件MIME类型', diff --git a/src/Oms.Application/Dtos/OpenApi/OpenApiQueryDtos.cs b/src/Oms.Application/Dtos/OpenApi/OpenApiQueryDtos.cs new file mode 100644 index 0000000..a1299f9 --- /dev/null +++ b/src/Oms.Application/Dtos/OpenApi/OpenApiQueryDtos.cs @@ -0,0 +1,92 @@ +namespace Oms.Application.Dtos.OpenApi; + +/// +/// 按客户单号 / 服务单号 / 跟踪号定位订单(至少填一项)。 +/// +public class OpenApiOrderLookupInput +{ + public string? ClientNo { get; set; } + + public string? ServiceNo { get; set; } + + public string? TrackNo { get; set; } +} + +public class OpenApiTrackQueryInput : OpenApiOrderLookupInput +{ +} + +public class OpenApiFilesGetInput : OpenApiOrderLookupInput +{ + /// + /// 附件类型:tradeProof / systemLabel / lastMileLabel / infraredImage / pod。不含 downstreamLabel。 + /// + public string? FileType { get; set; } +} + +public class OpenApiTrackQueryOutput +{ + public int OrderId { get; set; } + + public string? ClientNo { get; set; } + + public string? ServiceNo { get; set; } + + public string? TrackNo { get; set; } + + public List Events { get; set; } = []; +} + +public class OpenApiTrackEventDto +{ + public string? TrackCode { get; set; } + + public string? Status { get; set; } + + public string? Content { get; set; } + + public string? EnContent { get; set; } + + public string? Location { get; set; } + + public DateTime? Time { get; set; } + + public List Files { get; set; } = []; +} + +public class OpenApiTrackFileDto +{ + public string? FileName { get; set; } + + public string? Url { get; set; } +} + +public class OpenApiFilesGetOutput +{ + public int OrderId { get; set; } + + public string? ClientNo { get; set; } + + public string? ServiceNo { get; set; } + + public string FileType { get; set; } = string.Empty; + + public List Files { get; set; } = []; +} + +public class OpenApiFileItemDto +{ + public int Id { get; set; } + + public string? FileName { get; set; } + + public string FileUrl { get; set; } = string.Empty; + + public string? ContentType { get; set; } + + public long? FileSize { get; set; } + + public string? Source { get; set; } + + public DateTime CreateTime { get; set; } +} diff --git a/src/Oms.Application/Integrations/SmallBagTrack/ISmallBagTrackOrderClient.cs b/src/Oms.Application/Integrations/SmallBagTrack/ISmallBagTrackOrderClient.cs index c3e8190..15b8a0b 100644 --- a/src/Oms.Application/Integrations/SmallBagTrack/ISmallBagTrackOrderClient.cs +++ b/src/Oms.Application/Integrations/SmallBagTrack/ISmallBagTrackOrderClient.cs @@ -1,3 +1,5 @@ +using Oms.Application.Dtos.OpenApi; + namespace Oms.Application.Integrations.SmallBagTrack; public interface ISmallBagTrackOrderClient @@ -45,4 +47,11 @@ public interface ISmallBagTrackOrderClient int linkOrderType, string? authorization = null, CancellationToken cancellationToken = default); + + /// + /// 查询小包轨迹(POST ProviderService/Track/Query)。 + /// + Task> QuerySmallBagTracksAsync( + string orderNo, + CancellationToken cancellationToken = default); } diff --git a/src/Oms.Application/Integrations/SmallBagTrack/SmallBagTrackOrderClient.cs b/src/Oms.Application/Integrations/SmallBagTrack/SmallBagTrackOrderClient.cs index ecb80a1..1d1189f 100644 --- a/src/Oms.Application/Integrations/SmallBagTrack/SmallBagTrackOrderClient.cs +++ b/src/Oms.Application/Integrations/SmallBagTrack/SmallBagTrackOrderClient.cs @@ -1,8 +1,10 @@ using System.Net.Http.Headers; using System.Net.Http.Json; using System.Text.Json; +using Fake.Application; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; +using Oms.Application.Dtos.OpenApi; namespace Oms.Application.Integrations.SmallBagTrack; @@ -424,6 +426,178 @@ internal sealed class SmallBagTrackOrderClient( } } + public async Task> QuerySmallBagTracksAsync( + string orderNo, + CancellationToken cancellationToken = default) + { + if (string.IsNullOrWhiteSpace(orderNo)) + { + throw new BusinessException("服务单号为空,无法查询轨迹"); + } + + var settings = await configProvider.GetAsync(cancellationToken); + if (!settings.Enabled) + { + throw new BusinessException("轨迹服务未启用"); + } + + if (string.IsNullOrWhiteSpace(settings.BaseUrl)) + { + throw new BusinessException("轨迹服务未配置"); + } + + if (string.IsNullOrWhiteSpace(settings.AppKey) || string.IsNullOrWhiteSpace(settings.AppSecret)) + { + throw new BusinessException("轨迹服务未配置"); + } + + var appKey = settings.AppKey.Trim(); + var url = $"{settings.BaseUrl.Trim().TrimEnd('/')}/ProviderService/Track/Query"; + var body = new + { + appKey, + orderType = SmallBagTrackLifecycleEvents.OrderType, + orderNos = new[] { orderNo.Trim() } + }; + + var response = await SendSignedPostAsync( + settings, + appKey, + url, + body, + authorization: null, + cancellationToken); + + if (!response.Response.IsSuccessStatusCode) + { + throw new BusinessException("轨迹查询失败"); + } + + return ParseTrackEvents(response.ResponseBody); + } + + private static List ParseTrackEvents(string json) + { + if (string.IsNullOrWhiteSpace(json)) + { + return []; + } + + using var doc = JsonDocument.Parse(json); + var root = doc.RootElement; + var arr = root; + if (root.ValueKind == JsonValueKind.Object + && (root.TryGetProperty("data", out var data) || root.TryGetProperty("Data", out data))) + { + arr = data; + } + + if (arr.ValueKind != JsonValueKind.Array) + { + return []; + } + + var events = new List(); + foreach (var orderEl in arr.EnumerateArray()) + { + if (orderEl.ValueKind != JsonValueKind.Object) + { + continue; + } + + if (!orderEl.TryGetProperty("trackDetails", out var details) + && !orderEl.TryGetProperty("TrackDetails", out details)) + { + continue; + } + + if (details.ValueKind != JsonValueKind.Array) + { + continue; + } + + foreach (var el in details.EnumerateArray()) + { + var content = ReadJsonString(el, "content", "Content"); + if (string.IsNullOrWhiteSpace(content) || content == "--") + { + continue; + } + + events.Add(new OpenApiTrackEventDto + { + TrackCode = ReadJsonString(el, "code", "Code"), + Status = ReadJsonString(el, "status", "Status"), + Content = content, + EnContent = ReadJsonString(el, "enContent", "EnContent"), + Location = ReadJsonString(el, "location", "Location"), + Time = ReadJsonTime(el), + Files = ReadJsonFiles(el) + }); + } + } + + return events; + } + + private static List ReadJsonFiles(JsonElement el) + { + if (!el.TryGetProperty("files", out var files) && !el.TryGetProperty("Files", out files)) + { + return []; + } + + if (files.ValueKind != JsonValueKind.Array) + { + return []; + } + + var list = new List(); + foreach (var f in files.EnumerateArray()) + { + list.Add(new OpenApiTrackFileDto + { + FileName = ReadJsonString(f, "fileName", "FileName"), + Url = ReadJsonString(f, "downloadUrl", "DownloadUrl", "url", "Url") + }); + } + + return list; + } + + private static DateTime? ReadJsonTime(JsonElement el) + { + if (!el.TryGetProperty("time", out var time) && !el.TryGetProperty("Time", out time)) + { + return null; + } + + if (time.ValueKind == JsonValueKind.String && time.TryGetDateTime(out var dt)) + { + return dt; + } + + if (time.ValueKind == JsonValueKind.Number && time.TryGetInt64(out var unix)) + { + return DateTimeOffset.FromUnixTimeSeconds(unix > 10_000_000_000 ? unix / 1000 : unix).UtcDateTime; + } + + return null; + } + + private static string? ReadJsonString(JsonElement el, params string[] names) + { + foreach (var name in names) + { + if (el.TryGetProperty(name, out var p) && p.ValueKind == JsonValueKind.String) + { + return p.GetString(); + } + } + + return null; + } + private async Task SendSignedPostAsync( SmallBagTrackOptions settings, string appKey, diff --git a/src/Oms.Application/Services/Customers/CustomerOpenApiCredentialService.cs b/src/Oms.Application/Services/Customers/CustomerOpenApiCredentialService.cs index 1381ab6..787e720 100644 --- a/src/Oms.Application/Services/Customers/CustomerOpenApiCredentialService.cs +++ b/src/Oms.Application/Services/Customers/CustomerOpenApiCredentialService.cs @@ -181,13 +181,14 @@ public class CustomerOpenApiCredentialService : ApplicationService { if (!OmsSessionHelper.IsCustomerLogin(CurrentUser)) { - throw new BusinessException("仅客户门户可调用此接口"); + throw new BusinessException( + "当前是管理后台登录,不能调用客户门户接口。请到「客户」列表打开对应客户的开放接口凭证;客户请从客户门户登录后操作"); } var mapId = OmsSessionHelper.TryGetCustomerMapId(CurrentUser); if (!mapId.HasValue || mapId.Value <= 0) { - throw new BusinessException("客户未登录"); + throw new BusinessException("当前登录不是客户账号,无法读取开放接口凭证"); } var customer = await CustomerRepository.FirstOrDefaultAsync(x => x.UserId == mapId.Value); diff --git a/src/Oms.Application/Services/OpenApi/OpenApiService.cs b/src/Oms.Application/Services/OpenApi/OpenApiService.cs index 0076fbb..6d661e3 100644 --- a/src/Oms.Application/Services/OpenApi/OpenApiService.cs +++ b/src/Oms.Application/Services/OpenApi/OpenApiService.cs @@ -6,8 +6,10 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Oms.Application.Dtos.OpenApi; using Oms.Application.Dtos.Orders; +using Oms.Application.Integrations.SmallBagTrack; using Oms.Application.Services.Orders; using Oms.Application.Shared; +using Oms.Domain.OrdersAggregate.Consts; namespace Oms.Application.Services.OpenApi; @@ -33,6 +35,15 @@ public class OpenApiService : ApplicationService private OrderService OrderService => LazyServiceProvider.GetRequiredService(); + private OrderFileManager OrderFileManager => + LazyServiceProvider.GetRequiredService(); + + private OpenApiCustomerOrderResolver OrderResolver => + LazyServiceProvider.GetRequiredService(); + + private ISmallBagTrackOrderClient TrackClient => + LazyServiceProvider.GetRequiredService(); + private IHttpContextAccessor HttpContextAccessor => LazyServiceProvider.GetRequiredService(); @@ -84,7 +95,8 @@ public class OpenApiService : ApplicationService throw new BusinessException("请求体不能为空"); } - var data = await OrderService.CreateParcelAsync(input.ToOrderParcelCreateInput(customer.Id)); + var data = await OrderService.CreateParcelForCustomerOpenApiAsync( + input.ToOrderParcelCreateInput(customer.Id)); return OpenApiEnvelope.Ok(data); } catch (BusinessException ex) @@ -103,6 +115,131 @@ public class OpenApiService : ApplicationService } } + /// + /// 查询轨迹。Header 同创建包裹。按 clientNo / serviceNo / trackNo 定位本客户订单。 + /// + [AllowAnonymous] + [HttpPost("/openapi/track/query")] + public async Task> QueryTrackAsync( + [FromHeader(Name = CustomerOpenApiAuthHelper.AppKeyHeaderName)] string? appKey, + [FromHeader(Name = CustomerOpenApiAuthHelper.TimestampHeaderName)] string? timestamp, + [FromHeader(Name = CustomerOpenApiAuthHelper.SignHeaderName)] string? sign) + { + try + { + var rawBody = await ReadRawBodyAsync(); + var (customer, _) = await AuthHelper.ResolveSignedAsync(appKey, timestamp, sign, rawBody); + var input = DeserializeBody(rawBody); + var order = await OrderResolver.ResolveAsync(customer.Id, input); + if (string.IsNullOrWhiteSpace(order.ServiceNo)) + { + throw new BusinessException("订单无服务单号,无法查询轨迹"); + } + + var events = await TrackClient.QuerySmallBagTracksAsync(order.ServiceNo); + return OpenApiEnvelope.Ok(new OpenApiTrackQueryOutput + { + OrderId = order.Id, + ClientNo = order.ClientNo, + ServiceNo = order.ServiceNo, + TrackNo = order.TrackNo, + Events = events + }); + } + catch (BusinessException ex) + { + return OpenApiEnvelope.Fail(ex.Message); + } + catch (JsonException) + { + return OpenApiEnvelope.Fail("请求体格式错误"); + } + catch (Exception ex) + { + return OpenApiEnvelope.Fail( + string.IsNullOrWhiteSpace(ex.Message) ? "系统异常" : ex.Message, + "500"); + } + } + + /// + /// 按附件类型获取文件。Header 同创建包裹。 + /// fileType:tradeProof / systemLabel / lastMileLabel / infraredImage / pod。不含 downstreamLabel。 + /// + [AllowAnonymous] + [HttpPost("/openapi/files/get")] + public async Task> GetFilesAsync( + [FromHeader(Name = CustomerOpenApiAuthHelper.AppKeyHeaderName)] string? appKey, + [FromHeader(Name = CustomerOpenApiAuthHelper.TimestampHeaderName)] string? timestamp, + [FromHeader(Name = CustomerOpenApiAuthHelper.SignHeaderName)] string? sign) + { + try + { + var rawBody = await ReadRawBodyAsync(); + var (customer, _) = await AuthHelper.ResolveSignedAsync(appKey, timestamp, sign, rawBody); + var input = DeserializeBody(rawBody); + if (string.IsNullOrWhiteSpace(input.FileType)) + { + throw new BusinessException("请传入 fileType"); + } + + if (!OrderFileTypeCodes.IsCustomerOpenApiAllowed(input.FileType)) + { + throw new BusinessException("不支持的附件类型"); + } + + var order = await OrderResolver.ResolveAsync(customer.Id, input); + var files = await OrderFileManager.GetByTypeAsync(order.Id, input.FileType); + return OpenApiEnvelope.Ok(new OpenApiFilesGetOutput + { + OrderId = order.Id, + ClientNo = order.ClientNo, + ServiceNo = order.ServiceNo, + FileType = OrderFileTypeCodes.Normalize(input.FileType), + Files = files.Select(x => new OpenApiFileItemDto + { + Id = x.Id, + FileName = x.FileName, + FileUrl = x.FileUrl, + ContentType = x.ContentType, + FileSize = x.FileSize, + Source = x.Source, + CreateTime = x.CreateTime + }).ToList() + }); + } + catch (BusinessException ex) + { + return OpenApiEnvelope.Fail(ex.Message); + } + catch (JsonException) + { + return OpenApiEnvelope.Fail("请求体格式错误"); + } + catch (Exception ex) + { + return OpenApiEnvelope.Fail( + string.IsNullOrWhiteSpace(ex.Message) ? "系统异常" : ex.Message, + "500"); + } + } + + private T DeserializeBody(string rawBody) + { + if (string.IsNullOrWhiteSpace(rawBody)) + { + throw new BusinessException("请求体不能为空"); + } + + var input = JsonSerializer.Deserialize(rawBody, BodyJsonOptions); + if (input == null) + { + throw new BusinessException("请求体不能为空"); + } + + return input; + } + private async Task ReadRawBodyAsync() { var http = HttpContextAccessor.HttpContext diff --git a/src/Oms.Application/Services/Orders/OrderFileManager.cs b/src/Oms.Application/Services/Orders/OrderFileManager.cs index 62e273e..3cf553b 100644 --- a/src/Oms.Application/Services/Orders/OrderFileManager.cs +++ b/src/Oms.Application/Services/Orders/OrderFileManager.cs @@ -66,6 +66,25 @@ public sealed class OrderFileManager( return result; } + public async Task> GetByTypeAsync( + int orderId, + string fileType, + CancellationToken cancellationToken = default) + { + if (!OrderFileTypeCodes.IsValid(fileType)) + { + throw new BusinessException("不支持的附件类型"); + } + + var type = OrderFileTypeCodes.Normalize(fileType); + var list = await orderFileRepository.GetByOrderAndTypeListAsync(orderId, type, cancellationToken); + return list + .OrderBy(x => x.Sort) + .ThenBy(x => x.Id) + .Select(MapItem) + .ToList(); + } + public async Task GetGroupedAsync( int orderId, bool customerView, @@ -302,7 +321,8 @@ public sealed class OrderFileManager( or OrderFileTypeCodes.DownstreamLabel or OrderFileTypeCodes.LastMileLabel; var isInfraredOrProof = fileType is OrderFileTypeCodes.InfraredImage - or OrderFileTypeCodes.TradeProof; + or OrderFileTypeCodes.TradeProof + or OrderFileTypeCodes.Pod; if (isLabelType) { @@ -325,7 +345,7 @@ public sealed class OrderFileManager( { if (!ImageExt.Contains(ext) && !string.Equals(ext, ".pdf", StringComparison.OrdinalIgnoreCase)) { - throw new BusinessException("交易证明/红外图片仅支持图片或 pdf"); + throw new BusinessException("交易证明/红外图片/POD 仅支持图片或 pdf"); } if (file.Length > MaxPdfBytes) diff --git a/src/Oms.Application/Services/Orders/OrderService.cs b/src/Oms.Application/Services/Orders/OrderService.cs index f052699..4d7ab7f 100644 --- a/src/Oms.Application/Services/Orders/OrderService.cs +++ b/src/Oms.Application/Services/Orders/OrderService.cs @@ -531,6 +531,46 @@ public partial class OrderService : ApplicationService return output; } + /// + /// 客户 OpenAPI 创单:同一客户相同 clientNo 视为幂等,返回已存在订单,不新建。 + /// + public async Task CreateParcelForCustomerOpenApiAsync(OrderParcelCreateInput input) + { + NormalizeParcelCreateInput(input); + if (!string.IsNullOrWhiteSpace(input.ClientNo) && input.CustomerId is > 0) + { + var existing = await OrderRepository.GetByCustomerAndClientNoAsync( + input.CustomerId.Value, + input.ClientNo); + if (existing != null) + { + return await BuildParcelCreateOutputAsync(existing); + } + } + + try + { + return await CreateParcelAsync(input); + } + catch (BusinessException ex) when (ex.Message == "客户单号已存在") + { + if (string.IsNullOrWhiteSpace(input.ClientNo) || input.CustomerId is not > 0) + { + throw; + } + + var raced = await OrderRepository.GetByCustomerAndClientNoAsync( + input.CustomerId.Value, + input.ClientNo); + if (raced == null) + { + throw; + } + + return await BuildParcelCreateOutputAsync(raced); + } + } + private async Task TryAttachPickupAfterParcelCreateAsync( OrderParcelCreateInput input, OrderParcelCreateOutput output) diff --git a/src/Oms.Application/Shared/OpenApiCustomerOrderResolver.cs b/src/Oms.Application/Shared/OpenApiCustomerOrderResolver.cs new file mode 100644 index 0000000..565cbce --- /dev/null +++ b/src/Oms.Application/Shared/OpenApiCustomerOrderResolver.cs @@ -0,0 +1,54 @@ +using Fake.Application; +using Fake.DependencyInjection; +using Oms.Application.Dtos.OpenApi; +using Oms.Domain.OrdersAggregate.Entities; +using Oms.Domain.OrdersAggregate.Repositories; + +namespace Oms.Application.Shared; + +/// +/// OpenAPI 按客户凭证解析自有订单。 +/// +public class OpenApiCustomerOrderResolver : IScopedDependency +{ + private readonly IOrderRepository _orderRepository; + + public OpenApiCustomerOrderResolver(IOrderRepository orderRepository) + { + _orderRepository = orderRepository; + } + + public async Task ResolveAsync(int customerId, OpenApiOrderLookupInput input) + { + var clientNo = input.ClientNo?.Trim(); + var serviceNo = input.ServiceNo?.Trim(); + var trackNo = input.TrackNo?.Trim(); + if (string.IsNullOrWhiteSpace(clientNo) + && string.IsNullOrWhiteSpace(serviceNo) + && string.IsNullOrWhiteSpace(trackNo)) + { + throw new BusinessException("请传入 clientNo、serviceNo 或 trackNo"); + } + + Order? order = null; + if (!string.IsNullOrWhiteSpace(clientNo)) + { + order = await _orderRepository.GetByCustomerAndClientNoAsync(customerId, clientNo); + } + else if (!string.IsNullOrWhiteSpace(serviceNo)) + { + order = await _orderRepository.GetByServiceNoAsync(serviceNo); + } + else if (!string.IsNullOrWhiteSpace(trackNo)) + { + order = await _orderRepository.GetByTrackNoAsync(trackNo); + } + + if (order == null || order.CustomerId != customerId) + { + throw new BusinessException("订单不存在"); + } + + return order; + } +} diff --git a/src/Oms.Domain/OrdersAggregate/Consts/OrderFileTypeCodes.cs b/src/Oms.Domain/OrdersAggregate/Consts/OrderFileTypeCodes.cs index f82a107..986fd4c 100644 --- a/src/Oms.Domain/OrdersAggregate/Consts/OrderFileTypeCodes.cs +++ b/src/Oms.Domain/OrdersAggregate/Consts/OrderFileTypeCodes.cs @@ -10,6 +10,7 @@ public static class OrderFileTypeCodes public const string DownstreamLabel = "downstreamLabel"; public const string LastMileLabel = "lastMileLabel"; public const string InfraredImage = "infraredImage"; + public const string Pod = "pod"; public const int MaxFilesPerType = 20; @@ -19,7 +20,8 @@ public static class OrderFileTypeCodes SystemLabel, DownstreamLabel, LastMileLabel, - InfraredImage + InfraredImage, + Pod }; /// @@ -31,6 +33,19 @@ public static class OrderFileTypeCodes public static bool IsValid(string? fileType) => !string.IsNullOrWhiteSpace(fileType) && All.Contains(fileType.Trim()); + /// + /// 客户 OpenAPI 可查询的附件类型。不含下游面单。 + /// + public static bool IsCustomerOpenApiAllowed(string? fileType) + { + if (!IsValid(fileType)) + { + return false; + } + + return !string.Equals(fileType.Trim(), DownstreamLabel, StringComparison.OrdinalIgnoreCase); + } + public static string Normalize(string fileType) => fileType.Trim(); } diff --git a/src/Oms.Domain/OrdersAggregate/Entities/OrderFile.cs b/src/Oms.Domain/OrdersAggregate/Entities/OrderFile.cs index 3e6ecdd..a6105d8 100644 --- a/src/Oms.Domain/OrdersAggregate/Entities/OrderFile.cs +++ b/src/Oms.Domain/OrdersAggregate/Entities/OrderFile.cs @@ -9,7 +9,7 @@ public class OrderFile : OmsAuditedAggregateRoot public int OrderId { get; set; } /// - /// 附件类型:tradeProof / systemLabel / downstreamLabel / lastMileLabel / infraredImage。 + /// 附件类型:tradeProof / systemLabel / downstreamLabel / lastMileLabel / infraredImage / pod。 /// [SugarColumn(Length = 32)] public string FileType { get; set; } = string.Empty;