修复充值和查价
This commit is contained in:
parent
12ee25ad57
commit
00835bfadd
44
sql/migrate_t_order_parcel_columns.sql
Normal file
44
sql/migrate_t_order_parcel_columns.sql
Normal file
@ -0,0 +1,44 @@
|
||||
-- t_order / t_order_fee 小包预报计费字段补齐(可重复执行)
|
||||
-- 在 OMS 业务库执行,例如:mysql -h<host> -u<user> -p<db> < migrate_t_order_parcel_columns.sql
|
||||
|
||||
SET @db = DATABASE();
|
||||
|
||||
-- t_order.ExportCustomsMode
|
||||
SET @exist := (SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 't_order' AND COLUMN_NAME = 'ExportCustomsMode');
|
||||
SET @sql := IF(@exist = 0,
|
||||
'ALTER TABLE `t_order` ADD COLUMN `ExportCustomsMode` VARCHAR(32) NULL COMMENT ''出口报关方式'' AFTER `CargoAttribute`',
|
||||
'SELECT ''skip ExportCustomsMode''');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- t_order.PredeclareFailReason
|
||||
SET @exist := (SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 't_order' AND COLUMN_NAME = 'PredeclareFailReason');
|
||||
SET @sql := IF(@exist = 0,
|
||||
'ALTER TABLE `t_order` ADD COLUMN `PredeclareFailReason` VARCHAR(512) NULL COMMENT ''预报失败原因'' AFTER `IsRemote`',
|
||||
'SELECT ''skip PredeclareFailReason''');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- t_order.SalesProductRulesVersion
|
||||
SET @exist := (SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 't_order' AND COLUMN_NAME = 'SalesProductRulesVersion');
|
||||
SET @sql := IF(@exist = 0,
|
||||
'ALTER TABLE `t_order` ADD COLUMN `SalesProductRulesVersion` VARCHAR(64) NULL COMMENT ''销售产品规则快照版本'' AFTER `PredeclareFailReason`',
|
||||
'SELECT ''skip SalesProductRulesVersion''');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- t_order.SalesProductPriceVersion
|
||||
SET @exist := (SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 't_order' AND COLUMN_NAME = 'SalesProductPriceVersion');
|
||||
SET @sql := IF(@exist = 0,
|
||||
'ALTER TABLE `t_order` ADD COLUMN `SalesProductPriceVersion` VARCHAR(64) NULL COMMENT ''销售产品价格快照版本'' AFTER `SalesProductRulesVersion`',
|
||||
'SELECT ''skip SalesProductPriceVersion''');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- t_order_fee.ChargingStage
|
||||
SET @exist := (SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 't_order_fee' AND COLUMN_NAME = 'ChargingStage');
|
||||
SET @sql := IF(@exist = 0,
|
||||
'ALTER TABLE `t_order_fee` ADD COLUMN `ChargingStage` VARCHAR(16) NULL COMMENT ''计费阶段:forecast/inbound/outbound'' AFTER `PriceEffectiveTime`',
|
||||
'SELECT ''skip ChargingStage''');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
@ -1,11 +1,39 @@
|
||||
-- 小包预报计费:订单版本字段 + 费用计费阶段
|
||||
-- 若列已存在可跳过对应语句
|
||||
-- 与 migrate_t_order_parcel_columns.sql 相同,保留此文件名便于按文档引用
|
||||
-- t_order / t_order_fee 小包预报计费字段补齐(可重复执行)
|
||||
|
||||
ALTER TABLE `t_order`
|
||||
ADD COLUMN IF NOT EXISTS `ExportCustomsMode` VARCHAR(32) NULL COMMENT '出口报关方式' AFTER `CargoAttribute`,
|
||||
ADD COLUMN IF NOT EXISTS `PredeclareFailReason` VARCHAR(512) NULL COMMENT '预报失败原因' AFTER `IsRemote`,
|
||||
ADD COLUMN IF NOT EXISTS `SalesProductRulesVersion` VARCHAR(64) NULL COMMENT '销售产品规则快照版本' AFTER `PredeclareFailReason`,
|
||||
ADD COLUMN IF NOT EXISTS `SalesProductPriceVersion` VARCHAR(64) NULL COMMENT '销售产品价格快照版本' AFTER `SalesProductRulesVersion`;
|
||||
SET @db = DATABASE();
|
||||
|
||||
ALTER TABLE `t_order_fee`
|
||||
ADD COLUMN IF NOT EXISTS `ChargingStage` VARCHAR(16) NULL COMMENT '计费阶段:forecast/inbound/outbound' AFTER `PriceEffectiveTime`;
|
||||
SET @exist := (SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 't_order' AND COLUMN_NAME = 'ExportCustomsMode');
|
||||
SET @sql := IF(@exist = 0,
|
||||
'ALTER TABLE `t_order` ADD COLUMN `ExportCustomsMode` VARCHAR(32) NULL COMMENT ''出口报关方式'' AFTER `CargoAttribute`',
|
||||
'SELECT ''skip ExportCustomsMode''');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @exist := (SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 't_order' AND COLUMN_NAME = 'PredeclareFailReason');
|
||||
SET @sql := IF(@exist = 0,
|
||||
'ALTER TABLE `t_order` ADD COLUMN `PredeclareFailReason` VARCHAR(512) NULL COMMENT ''预报失败原因'' AFTER `IsRemote`',
|
||||
'SELECT ''skip PredeclareFailReason''');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @exist := (SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 't_order' AND COLUMN_NAME = 'SalesProductRulesVersion');
|
||||
SET @sql := IF(@exist = 0,
|
||||
'ALTER TABLE `t_order` ADD COLUMN `SalesProductRulesVersion` VARCHAR(64) NULL COMMENT ''销售产品规则快照版本'' AFTER `PredeclareFailReason`',
|
||||
'SELECT ''skip SalesProductRulesVersion''');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @exist := (SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 't_order' AND COLUMN_NAME = 'SalesProductPriceVersion');
|
||||
SET @sql := IF(@exist = 0,
|
||||
'ALTER TABLE `t_order` ADD COLUMN `SalesProductPriceVersion` VARCHAR(64) NULL COMMENT ''销售产品价格快照版本'' AFTER `SalesProductRulesVersion`',
|
||||
'SELECT ''skip SalesProductPriceVersion''');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @exist := (SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 't_order_fee' AND COLUMN_NAME = 'ChargingStage');
|
||||
SET @sql := IF(@exist = 0,
|
||||
'ALTER TABLE `t_order_fee` ADD COLUMN `ChargingStage` VARCHAR(16) NULL COMMENT ''计费阶段:forecast/inbound/outbound'' AFTER `PriceEffectiveTime`',
|
||||
'SELECT ''skip ChargingStage''');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
@ -24,8 +24,7 @@ public class SubmitRechargeFormInput
|
||||
public string? Remark { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 充值截图文件。
|
||||
/// 充值截图文件(可选;未上传时存 "0")。
|
||||
/// </summary>
|
||||
[Required]
|
||||
public IFormFile RechargeScreenshot { get; set; } = null!;
|
||||
public IFormFile? RechargeScreenshot { get; set; }
|
||||
}
|
||||
|
||||
@ -413,7 +413,12 @@ public partial class OrderService : ApplicationService
|
||||
/// <returns>出参:订单查询表达式。</returns>
|
||||
private Expression<Func<Order, bool>> BuildPaginatedPredicate(OrderPaginatedInput input)
|
||||
{
|
||||
return BuildOrderPredicate(input.Status, input.Keyword, input.CreateTimeStart, input.CreateTimeEnd);
|
||||
return BuildOrderPredicate(
|
||||
input.Status,
|
||||
input.Keyword,
|
||||
input.CreateTimeStart,
|
||||
input.CreateTimeEnd,
|
||||
OmsSessionHelper.TryGetCustomerCreateUserId(CurrentUser));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -423,7 +428,12 @@ public partial class OrderService : ApplicationService
|
||||
/// <returns>出参:订单查询表达式。</returns>
|
||||
private Expression<Func<Order, bool>> BuildExportPredicate(OrderExportInput input)
|
||||
{
|
||||
return BuildOrderPredicate(input.Status, input.Keyword, input.CreateTimeStart, input.CreateTimeEnd);
|
||||
return BuildOrderPredicate(
|
||||
input.Status,
|
||||
input.Keyword,
|
||||
input.CreateTimeStart,
|
||||
input.CreateTimeEnd,
|
||||
OmsSessionHelper.TryGetCustomerCreateUserId(CurrentUser));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -445,12 +455,15 @@ public partial class OrderService : ApplicationService
|
||||
string? status,
|
||||
string? keyword,
|
||||
DateTime? createTimeStart,
|
||||
DateTime? createTimeEnd)
|
||||
DateTime? createTimeEnd,
|
||||
int? createUserId = null)
|
||||
{
|
||||
status = status?.Trim();
|
||||
keyword = keyword?.Trim();
|
||||
var hasStatusFilter = !string.IsNullOrWhiteSpace(status) && status != "all";
|
||||
var hasKeywordFilter = !string.IsNullOrWhiteSpace(keyword);
|
||||
var hasCreateUserFilter = createUserId.HasValue;
|
||||
var createUserIdValue = createUserId.GetValueOrDefault();
|
||||
|
||||
// 显式拆开时间分支,避免 SqlSugar 在翻译 Nullable<DateTime> 判空时生成异常 SQL。
|
||||
if (createTimeStart.HasValue && createTimeEnd.HasValue)
|
||||
@ -458,6 +471,7 @@ public partial class OrderService : ApplicationService
|
||||
var start = createTimeStart.Value;
|
||||
var end = createTimeEnd.Value;
|
||||
return order =>
|
||||
(!hasCreateUserFilter || order.CreateUserId == createUserIdValue) &&
|
||||
(!hasStatusFilter || order.OrderStatus == status) &&
|
||||
(!hasKeywordFilter ||
|
||||
order.ServiceNo.Contains(keyword!) ||
|
||||
@ -471,6 +485,7 @@ public partial class OrderService : ApplicationService
|
||||
{
|
||||
var start = createTimeStart.Value;
|
||||
return order =>
|
||||
(!hasCreateUserFilter || order.CreateUserId == createUserIdValue) &&
|
||||
(!hasStatusFilter || order.OrderStatus == status) &&
|
||||
(!hasKeywordFilter ||
|
||||
order.ServiceNo.Contains(keyword!) ||
|
||||
@ -483,6 +498,7 @@ public partial class OrderService : ApplicationService
|
||||
{
|
||||
var end = createTimeEnd.Value;
|
||||
return order =>
|
||||
(!hasCreateUserFilter || order.CreateUserId == createUserIdValue) &&
|
||||
(!hasStatusFilter || order.OrderStatus == status) &&
|
||||
(!hasKeywordFilter ||
|
||||
order.ServiceNo.Contains(keyword!) ||
|
||||
@ -492,6 +508,7 @@ public partial class OrderService : ApplicationService
|
||||
}
|
||||
|
||||
return order =>
|
||||
(!hasCreateUserFilter || order.CreateUserId == createUserIdValue) &&
|
||||
(!hasStatusFilter || order.OrderStatus == status) &&
|
||||
(!hasKeywordFilter ||
|
||||
order.ServiceNo.Contains(keyword!) ||
|
||||
@ -499,6 +516,20 @@ public partial class OrderService : ApplicationService
|
||||
(order.TrackNo ?? string.Empty).Contains(keyword!));
|
||||
}
|
||||
|
||||
private void EnsureCustomerCanAccessOrder(Order order)
|
||||
{
|
||||
var createUserId = OmsSessionHelper.TryGetCustomerCreateUserId(CurrentUser);
|
||||
if (createUserId is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (order.CreateUserId != createUserId.Value)
|
||||
{
|
||||
throw new BusinessException("无权访问该订单");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<Order> GetOrderOrThrowAsync(int id)
|
||||
{
|
||||
var entity = await OrderRepository.FirstOrDefaultAsync(x => x.Id == id);
|
||||
@ -507,6 +538,7 @@ public partial class OrderService : ApplicationService
|
||||
throw new BusinessException("订单不存在");
|
||||
}
|
||||
|
||||
EnsureCustomerCanAccessOrder(entity);
|
||||
return entity;
|
||||
}
|
||||
|
||||
|
||||
@ -30,17 +30,10 @@ public class RechargeService : ApplicationService
|
||||
[Consumes("multipart/form-data")]
|
||||
public async Task<RechargeRecordOutput> SubmitAsync([FromForm] SubmitRechargeFormInput input)
|
||||
{
|
||||
var customerId = input.CustomerId ?? CurrentUser.GetId<int>();
|
||||
if (customerId <= 0)
|
||||
{
|
||||
throw new BusinessException("客户ID不能为空");
|
||||
}
|
||||
|
||||
var customer = await CustomerRepository.FirstOrDefaultAsync(x => x.Id == customerId);
|
||||
if (customer == null)
|
||||
{
|
||||
throw new BusinessException("客户不存在");
|
||||
}
|
||||
var customer = await OmsSessionHelper.ResolveSessionCustomerAsync(
|
||||
CustomerRepository,
|
||||
CurrentUser,
|
||||
input.CustomerId);
|
||||
|
||||
if (input.RechargeAmount < 0.01M)
|
||||
{
|
||||
@ -148,7 +141,11 @@ public class RechargeService : ApplicationService
|
||||
[HttpGet("/recharge/recharge-record/list")]
|
||||
public async Task<OmsPageResult<RechargeRecordPaginatedOutput>> GetPaginatedListAsync([FromQuery] RechargeRecordPaginatedInput input)
|
||||
{
|
||||
var predicate = BuildPaginatedPredicate(input);
|
||||
var customerId = await OmsSessionHelper.ResolveListCustomerIdAsync(
|
||||
CustomerRepository,
|
||||
CurrentUser,
|
||||
input.CustomerId);
|
||||
var predicate = BuildPaginatedPredicate(input, customerId);
|
||||
var pageIndex = OmsDocHelper.NormalizePage(input.Page);
|
||||
var pageSize = OmsDocHelper.NormalizePageSize(input.PageSize);
|
||||
|
||||
@ -203,7 +200,11 @@ public class RechargeService : ApplicationService
|
||||
[HttpGet("/recharge/export")]
|
||||
public async Task<FileContentResult> ExportAsync([FromQuery] RechargeExportInput input)
|
||||
{
|
||||
var records = await RechargeRecordRepository.GetListAsync(BuildExportPredicate(input));
|
||||
var customerId = await OmsSessionHelper.ResolveListCustomerIdAsync(
|
||||
CustomerRepository,
|
||||
CurrentUser,
|
||||
input.CustomerId);
|
||||
var records = await RechargeRecordRepository.GetListAsync(BuildExportPredicate(input, customerId));
|
||||
var customerIds = records.Select(x => x.CustomerId).Distinct().ToArray();
|
||||
var customers = customerIds.Length == 0
|
||||
? []
|
||||
@ -245,10 +246,12 @@ public class RechargeService : ApplicationService
|
||||
/// </summary>
|
||||
/// <param name="input">入参:充值分页查询条件对象。</param>
|
||||
/// <returns>出参:充值记录查询表达式。</returns>
|
||||
private Expression<Func<RechargeRecord, bool>> BuildPaginatedPredicate(RechargeRecordPaginatedInput input)
|
||||
private Expression<Func<RechargeRecord, bool>> BuildPaginatedPredicate(
|
||||
RechargeRecordPaginatedInput input,
|
||||
int? customerId)
|
||||
{
|
||||
var serialNumber = input.SerialNumber?.Trim();
|
||||
return BuildRechargePredicate(input.CustomerId, input.RechargeStatus, serialNumber, input.StartTime, input.EndTime);
|
||||
return BuildRechargePredicate(customerId, input.RechargeStatus, serialNumber, input.StartTime, input.EndTime);
|
||||
}
|
||||
|
||||
private async Task<RechargeRecord> GetRechargeRecordOrThrowAsync(int id)
|
||||
@ -302,9 +305,9 @@ public class RechargeService : ApplicationService
|
||||
/// </summary>
|
||||
/// <param name="input">入参:充值导出筛选条件对象。</param>
|
||||
/// <returns>出参:充值记录查询表达式。</returns>
|
||||
private Expression<Func<RechargeRecord, bool>> BuildExportPredicate(RechargeExportInput input)
|
||||
private Expression<Func<RechargeRecord, bool>> BuildExportPredicate(RechargeExportInput input, int? customerId)
|
||||
{
|
||||
return BuildRechargePredicate(input.CustomerId, null, null, input.StartTime, input.EndTime);
|
||||
return BuildRechargePredicate(customerId, null, null, input.StartTime, input.EndTime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using Oms.Domain.OrdersAggregate.Consts;
|
||||
using Oms.Domain.SystemAggregate.Consts;
|
||||
|
||||
@ -6,12 +7,20 @@ namespace Oms.Application.Shared;
|
||||
|
||||
public static class OmsDocHelper
|
||||
{
|
||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||
{
|
||||
PropertyNameCaseInsensitive = true
|
||||
};
|
||||
|
||||
public static int NormalizePage(int page) => page <= 0 ? 1 : page;
|
||||
|
||||
public static int NormalizePageSize(int pageSize) => pageSize <= 0 ? 20 : Math.Min(pageSize, 200);
|
||||
|
||||
public static DateTime? NullIfDefault(DateTime value) => value == default ? null : value;
|
||||
|
||||
/// <summary>
|
||||
/// 将库内 JSON 字符串转为 API 可输出的 CLR 对象(避免 JsonElement 被 Newtonsoft 序列化成 valueKind)。
|
||||
/// </summary>
|
||||
public static object? DeserializeJsonObject(string? json)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(json))
|
||||
@ -19,7 +28,8 @@ public static class OmsDocHelper
|
||||
return null;
|
||||
}
|
||||
|
||||
return JsonSerializer.Deserialize<object>(json);
|
||||
using var document = JsonDocument.Parse(json);
|
||||
return ConvertJsonElement(document.RootElement);
|
||||
}
|
||||
|
||||
public static T? DeserializeJson<T>(string? json)
|
||||
@ -29,19 +39,62 @@ public static class OmsDocHelper
|
||||
return default;
|
||||
}
|
||||
|
||||
return JsonSerializer.Deserialize<T>(json);
|
||||
return JsonSerializer.Deserialize<T>(json, JsonOptions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将请求体中的动态 JSON(JsonElement / JToken / Dictionary 等)规范为可落库的 JSON 字符串。
|
||||
/// </summary>
|
||||
public static string? SerializeJson<T>(T? value)
|
||||
{
|
||||
if (value == null)
|
||||
if (value is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return JsonSerializer.Serialize(value);
|
||||
if (value is JsonElement element)
|
||||
{
|
||||
return element.ValueKind is JsonValueKind.Null or JsonValueKind.Undefined
|
||||
? null
|
||||
: element.GetRawText();
|
||||
}
|
||||
|
||||
if (value is JsonNode node)
|
||||
{
|
||||
return node.ToJsonString();
|
||||
}
|
||||
|
||||
if (value is string text)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(text) ? null : text;
|
||||
}
|
||||
|
||||
var typeName = value.GetType().FullName;
|
||||
if (typeName is not null && typeName.StartsWith("Newtonsoft.Json.Linq.", StringComparison.Ordinal))
|
||||
{
|
||||
return value.ToString();
|
||||
}
|
||||
|
||||
return JsonSerializer.Serialize(value, JsonOptions);
|
||||
}
|
||||
|
||||
private static object? ConvertJsonElement(JsonElement element) => element.ValueKind switch
|
||||
{
|
||||
JsonValueKind.Object => element.EnumerateObject()
|
||||
.ToDictionary(property => property.Name, property => ConvertJsonElement(property.Value)),
|
||||
JsonValueKind.Array => element.EnumerateArray()
|
||||
.Select(ConvertJsonElement)
|
||||
.ToList(),
|
||||
JsonValueKind.String => element.GetString(),
|
||||
JsonValueKind.Number => element.TryGetInt64(out var integer)
|
||||
? integer
|
||||
: element.GetDecimal(),
|
||||
JsonValueKind.True => true,
|
||||
JsonValueKind.False => false,
|
||||
JsonValueKind.Null or JsonValueKind.Undefined => null,
|
||||
_ => element.GetRawText()
|
||||
};
|
||||
|
||||
public static List<string> DeserializeStringList(string? json) => JsonStringArrayHelper.Deserialize(json);
|
||||
|
||||
public static string? GetOrderStatusName(string statusCode)
|
||||
|
||||
@ -17,11 +17,11 @@ public static class OmsFileUploadHelper
|
||||
/// <summary>
|
||||
/// 保存充值截图,返回以 /uploads 开头的相对访问路径。
|
||||
/// </summary>
|
||||
public static async Task<string> SaveRechargeScreenshotAsync(IFormFile file, IWebHostEnvironment env)
|
||||
public static async Task<string> SaveRechargeScreenshotAsync(IFormFile? file, IWebHostEnvironment env)
|
||||
{
|
||||
if (file == null || file.Length <= 0)
|
||||
{
|
||||
throw new BusinessException("请上传充值截图");
|
||||
return "0";
|
||||
}
|
||||
|
||||
if (file.Length > MaxImageBytes)
|
||||
|
||||
105
src/Oms.Application/Shared/OmsSessionHelper.cs
Normal file
105
src/Oms.Application/Shared/OmsSessionHelper.cs
Normal file
@ -0,0 +1,105 @@
|
||||
using Fake.Application;
|
||||
using Fake.Users;
|
||||
using Oms.Domain.CustomersAggregate.Entities;
|
||||
using Oms.Domain.CustomersAggregate.Repositories;
|
||||
|
||||
namespace Oms.Application.Shared;
|
||||
|
||||
/// <summary>
|
||||
/// 从 JWT Claims 解析当前会话类型(管理员 / 客户)。
|
||||
/// </summary>
|
||||
public static class OmsSessionHelper
|
||||
{
|
||||
public const string LoginTypeClaim = "loginType";
|
||||
|
||||
private const string CustomerLoginType = "1";
|
||||
|
||||
public static bool IsCustomerLogin(ICurrentUser currentUser) =>
|
||||
string.Equals(
|
||||
currentUser.FindClaimOrNull(LoginTypeClaim)?.Value,
|
||||
CustomerLoginType,
|
||||
StringComparison.Ordinal);
|
||||
|
||||
/// <summary>
|
||||
/// 客户登录时返回 mapId(对应 t_customer.UserId / 订单 CreateUserId);管理员返回 null。
|
||||
/// </summary>
|
||||
public static int? TryGetCustomerMapId(ICurrentUser currentUser)
|
||||
{
|
||||
if (!IsCustomerLogin(currentUser))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var userId = currentUser.GetId<int>();
|
||||
return userId > 0 ? userId : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 客户登录时返回 mapId(对应订单 CreateUserId);管理员返回 null(不过滤)。
|
||||
/// </summary>
|
||||
public static int? TryGetCustomerCreateUserId(ICurrentUser currentUser) =>
|
||||
TryGetCustomerMapId(currentUser);
|
||||
|
||||
/// <summary>
|
||||
/// 解析当前会话对应的 t_customer 记录(客户按 UserId=mapId;管理员可按 CustomerId 代提交)。
|
||||
/// </summary>
|
||||
public static async Task<Customer> ResolveSessionCustomerAsync(
|
||||
ICustomerRepository customerRepository,
|
||||
ICurrentUser currentUser,
|
||||
int? requestedCustomerId = null)
|
||||
{
|
||||
var mapId = TryGetCustomerMapId(currentUser);
|
||||
if (mapId.HasValue)
|
||||
{
|
||||
var sessionCustomer = await customerRepository.FirstOrDefaultAsync(x => x.UserId == mapId.Value);
|
||||
if (sessionCustomer == null)
|
||||
{
|
||||
throw new BusinessException("未找到客户信息");
|
||||
}
|
||||
|
||||
if (requestedCustomerId is > 0 && requestedCustomerId.Value != sessionCustomer.Id)
|
||||
{
|
||||
throw new BusinessException("无权为该客户提交充值");
|
||||
}
|
||||
|
||||
return sessionCustomer;
|
||||
}
|
||||
|
||||
var customerId = requestedCustomerId ?? currentUser.GetId<int>();
|
||||
if (customerId <= 0)
|
||||
{
|
||||
throw new BusinessException("客户ID不能为空");
|
||||
}
|
||||
|
||||
var customer = await customerRepository.FirstOrDefaultAsync(x => x.Id == customerId);
|
||||
if (customer == null)
|
||||
{
|
||||
throw new BusinessException("客户不存在");
|
||||
}
|
||||
|
||||
return customer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 客户登录时强制返回 t_customer.Id;管理员返回入参 customerId。
|
||||
/// </summary>
|
||||
public static async Task<int?> ResolveListCustomerIdAsync(
|
||||
ICustomerRepository customerRepository,
|
||||
ICurrentUser currentUser,
|
||||
int? requestedCustomerId)
|
||||
{
|
||||
var mapId = TryGetCustomerMapId(currentUser);
|
||||
if (!mapId.HasValue)
|
||||
{
|
||||
return requestedCustomerId;
|
||||
}
|
||||
|
||||
var customer = await customerRepository.FirstOrDefaultAsync(x => x.UserId == mapId.Value);
|
||||
if (customer == null)
|
||||
{
|
||||
throw new BusinessException("未找到客户信息");
|
||||
}
|
||||
|
||||
return customer.Id;
|
||||
}
|
||||
}
|
||||
@ -56,6 +56,26 @@ public class OmsDocHelperTests
|
||||
result["amount"].GetDecimal().Should().Be(128.5m);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SerializeJson_ShouldPreserveJsonElementObjectInsteadOfValueKind()
|
||||
{
|
||||
using var document = JsonDocument.Parse("""{"分区":"3","重量":"1.5"}""");
|
||||
var json = OmsDocHelper.SerializeJson(document.RootElement);
|
||||
|
||||
json.Should().Be("""{"分区":"3","重量":"1.5"}""");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeserializeJsonObject_ShouldReturnDictionaryNotJsonElement()
|
||||
{
|
||||
var result = OmsDocHelper.DeserializeJsonObject("""{"普通客户":1,"VIP1":2}""");
|
||||
|
||||
result.Should().BeOfType<Dictionary<string, object?>>();
|
||||
var map = (Dictionary<string, object?>)result!;
|
||||
map["普通客户"].Should().Be(1L);
|
||||
map["VIP1"].Should().Be(2L);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeserializeStringList_ShouldReturnEmptyListWhenJsonMissing()
|
||||
{
|
||||
|
||||
Loading…
Reference in New Issue
Block a user