修复充值和查价
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`
|
SET @db = DATABASE();
|
||||||
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`;
|
|
||||||
|
|
||||||
ALTER TABLE `t_order_fee`
|
SET @exist := (SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||||
ADD COLUMN IF NOT EXISTS `ChargingStage` VARCHAR(16) NULL COMMENT '计费阶段:forecast/inbound/outbound' AFTER `PriceEffectiveTime`;
|
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; }
|
public string? Remark { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 充值截图文件。
|
/// 充值截图文件(可选;未上传时存 "0")。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Required]
|
public IFormFile? RechargeScreenshot { get; set; }
|
||||||
public IFormFile RechargeScreenshot { get; set; } = null!;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -413,7 +413,12 @@ public partial class OrderService : ApplicationService
|
|||||||
/// <returns>出参:订单查询表达式。</returns>
|
/// <returns>出参:订单查询表达式。</returns>
|
||||||
private Expression<Func<Order, bool>> BuildPaginatedPredicate(OrderPaginatedInput input)
|
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>
|
/// <summary>
|
||||||
@ -423,7 +428,12 @@ public partial class OrderService : ApplicationService
|
|||||||
/// <returns>出参:订单查询表达式。</returns>
|
/// <returns>出参:订单查询表达式。</returns>
|
||||||
private Expression<Func<Order, bool>> BuildExportPredicate(OrderExportInput input)
|
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>
|
/// <summary>
|
||||||
@ -445,12 +455,15 @@ public partial class OrderService : ApplicationService
|
|||||||
string? status,
|
string? status,
|
||||||
string? keyword,
|
string? keyword,
|
||||||
DateTime? createTimeStart,
|
DateTime? createTimeStart,
|
||||||
DateTime? createTimeEnd)
|
DateTime? createTimeEnd,
|
||||||
|
int? createUserId = null)
|
||||||
{
|
{
|
||||||
status = status?.Trim();
|
status = status?.Trim();
|
||||||
keyword = keyword?.Trim();
|
keyword = keyword?.Trim();
|
||||||
var hasStatusFilter = !string.IsNullOrWhiteSpace(status) && status != "all";
|
var hasStatusFilter = !string.IsNullOrWhiteSpace(status) && status != "all";
|
||||||
var hasKeywordFilter = !string.IsNullOrWhiteSpace(keyword);
|
var hasKeywordFilter = !string.IsNullOrWhiteSpace(keyword);
|
||||||
|
var hasCreateUserFilter = createUserId.HasValue;
|
||||||
|
var createUserIdValue = createUserId.GetValueOrDefault();
|
||||||
|
|
||||||
// 显式拆开时间分支,避免 SqlSugar 在翻译 Nullable<DateTime> 判空时生成异常 SQL。
|
// 显式拆开时间分支,避免 SqlSugar 在翻译 Nullable<DateTime> 判空时生成异常 SQL。
|
||||||
if (createTimeStart.HasValue && createTimeEnd.HasValue)
|
if (createTimeStart.HasValue && createTimeEnd.HasValue)
|
||||||
@ -458,6 +471,7 @@ public partial class OrderService : ApplicationService
|
|||||||
var start = createTimeStart.Value;
|
var start = createTimeStart.Value;
|
||||||
var end = createTimeEnd.Value;
|
var end = createTimeEnd.Value;
|
||||||
return order =>
|
return order =>
|
||||||
|
(!hasCreateUserFilter || order.CreateUserId == createUserIdValue) &&
|
||||||
(!hasStatusFilter || order.OrderStatus == status) &&
|
(!hasStatusFilter || order.OrderStatus == status) &&
|
||||||
(!hasKeywordFilter ||
|
(!hasKeywordFilter ||
|
||||||
order.ServiceNo.Contains(keyword!) ||
|
order.ServiceNo.Contains(keyword!) ||
|
||||||
@ -471,6 +485,7 @@ public partial class OrderService : ApplicationService
|
|||||||
{
|
{
|
||||||
var start = createTimeStart.Value;
|
var start = createTimeStart.Value;
|
||||||
return order =>
|
return order =>
|
||||||
|
(!hasCreateUserFilter || order.CreateUserId == createUserIdValue) &&
|
||||||
(!hasStatusFilter || order.OrderStatus == status) &&
|
(!hasStatusFilter || order.OrderStatus == status) &&
|
||||||
(!hasKeywordFilter ||
|
(!hasKeywordFilter ||
|
||||||
order.ServiceNo.Contains(keyword!) ||
|
order.ServiceNo.Contains(keyword!) ||
|
||||||
@ -483,6 +498,7 @@ public partial class OrderService : ApplicationService
|
|||||||
{
|
{
|
||||||
var end = createTimeEnd.Value;
|
var end = createTimeEnd.Value;
|
||||||
return order =>
|
return order =>
|
||||||
|
(!hasCreateUserFilter || order.CreateUserId == createUserIdValue) &&
|
||||||
(!hasStatusFilter || order.OrderStatus == status) &&
|
(!hasStatusFilter || order.OrderStatus == status) &&
|
||||||
(!hasKeywordFilter ||
|
(!hasKeywordFilter ||
|
||||||
order.ServiceNo.Contains(keyword!) ||
|
order.ServiceNo.Contains(keyword!) ||
|
||||||
@ -492,6 +508,7 @@ public partial class OrderService : ApplicationService
|
|||||||
}
|
}
|
||||||
|
|
||||||
return order =>
|
return order =>
|
||||||
|
(!hasCreateUserFilter || order.CreateUserId == createUserIdValue) &&
|
||||||
(!hasStatusFilter || order.OrderStatus == status) &&
|
(!hasStatusFilter || order.OrderStatus == status) &&
|
||||||
(!hasKeywordFilter ||
|
(!hasKeywordFilter ||
|
||||||
order.ServiceNo.Contains(keyword!) ||
|
order.ServiceNo.Contains(keyword!) ||
|
||||||
@ -499,6 +516,20 @@ public partial class OrderService : ApplicationService
|
|||||||
(order.TrackNo ?? string.Empty).Contains(keyword!));
|
(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)
|
private async Task<Order> GetOrderOrThrowAsync(int id)
|
||||||
{
|
{
|
||||||
var entity = await OrderRepository.FirstOrDefaultAsync(x => x.Id == id);
|
var entity = await OrderRepository.FirstOrDefaultAsync(x => x.Id == id);
|
||||||
@ -507,6 +538,7 @@ public partial class OrderService : ApplicationService
|
|||||||
throw new BusinessException("订单不存在");
|
throw new BusinessException("订单不存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EnsureCustomerCanAccessOrder(entity);
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -30,17 +30,10 @@ public class RechargeService : ApplicationService
|
|||||||
[Consumes("multipart/form-data")]
|
[Consumes("multipart/form-data")]
|
||||||
public async Task<RechargeRecordOutput> SubmitAsync([FromForm] SubmitRechargeFormInput input)
|
public async Task<RechargeRecordOutput> SubmitAsync([FromForm] SubmitRechargeFormInput input)
|
||||||
{
|
{
|
||||||
var customerId = input.CustomerId ?? CurrentUser.GetId<int>();
|
var customer = await OmsSessionHelper.ResolveSessionCustomerAsync(
|
||||||
if (customerId <= 0)
|
CustomerRepository,
|
||||||
{
|
CurrentUser,
|
||||||
throw new BusinessException("客户ID不能为空");
|
input.CustomerId);
|
||||||
}
|
|
||||||
|
|
||||||
var customer = await CustomerRepository.FirstOrDefaultAsync(x => x.Id == customerId);
|
|
||||||
if (customer == null)
|
|
||||||
{
|
|
||||||
throw new BusinessException("客户不存在");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (input.RechargeAmount < 0.01M)
|
if (input.RechargeAmount < 0.01M)
|
||||||
{
|
{
|
||||||
@ -148,7 +141,11 @@ public class RechargeService : ApplicationService
|
|||||||
[HttpGet("/recharge/recharge-record/list")]
|
[HttpGet("/recharge/recharge-record/list")]
|
||||||
public async Task<OmsPageResult<RechargeRecordPaginatedOutput>> GetPaginatedListAsync([FromQuery] RechargeRecordPaginatedInput input)
|
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 pageIndex = OmsDocHelper.NormalizePage(input.Page);
|
||||||
var pageSize = OmsDocHelper.NormalizePageSize(input.PageSize);
|
var pageSize = OmsDocHelper.NormalizePageSize(input.PageSize);
|
||||||
|
|
||||||
@ -203,7 +200,11 @@ public class RechargeService : ApplicationService
|
|||||||
[HttpGet("/recharge/export")]
|
[HttpGet("/recharge/export")]
|
||||||
public async Task<FileContentResult> ExportAsync([FromQuery] RechargeExportInput input)
|
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 customerIds = records.Select(x => x.CustomerId).Distinct().ToArray();
|
||||||
var customers = customerIds.Length == 0
|
var customers = customerIds.Length == 0
|
||||||
? []
|
? []
|
||||||
@ -245,10 +246,12 @@ public class RechargeService : ApplicationService
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="input">入参:充值分页查询条件对象。</param>
|
/// <param name="input">入参:充值分页查询条件对象。</param>
|
||||||
/// <returns>出参:充值记录查询表达式。</returns>
|
/// <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();
|
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)
|
private async Task<RechargeRecord> GetRechargeRecordOrThrowAsync(int id)
|
||||||
@ -302,9 +305,9 @@ public class RechargeService : ApplicationService
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="input">入参:充值导出筛选条件对象。</param>
|
/// <param name="input">入参:充值导出筛选条件对象。</param>
|
||||||
/// <returns>出参:充值记录查询表达式。</returns>
|
/// <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>
|
/// <summary>
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
using Oms.Domain.OrdersAggregate.Consts;
|
using Oms.Domain.OrdersAggregate.Consts;
|
||||||
using Oms.Domain.SystemAggregate.Consts;
|
using Oms.Domain.SystemAggregate.Consts;
|
||||||
|
|
||||||
@ -6,12 +7,20 @@ namespace Oms.Application.Shared;
|
|||||||
|
|
||||||
public static class OmsDocHelper
|
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 NormalizePage(int page) => page <= 0 ? 1 : page;
|
||||||
|
|
||||||
public static int NormalizePageSize(int pageSize) => pageSize <= 0 ? 20 : Math.Min(pageSize, 200);
|
public static int NormalizePageSize(int pageSize) => pageSize <= 0 ? 20 : Math.Min(pageSize, 200);
|
||||||
|
|
||||||
public static DateTime? NullIfDefault(DateTime value) => value == default ? null : value;
|
public static DateTime? NullIfDefault(DateTime value) => value == default ? null : value;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将库内 JSON 字符串转为 API 可输出的 CLR 对象(避免 JsonElement 被 Newtonsoft 序列化成 valueKind)。
|
||||||
|
/// </summary>
|
||||||
public static object? DeserializeJsonObject(string? json)
|
public static object? DeserializeJsonObject(string? json)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(json))
|
if (string.IsNullOrWhiteSpace(json))
|
||||||
@ -19,7 +28,8 @@ public static class OmsDocHelper
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return JsonSerializer.Deserialize<object>(json);
|
using var document = JsonDocument.Parse(json);
|
||||||
|
return ConvertJsonElement(document.RootElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static T? DeserializeJson<T>(string? json)
|
public static T? DeserializeJson<T>(string? json)
|
||||||
@ -29,19 +39,62 @@ public static class OmsDocHelper
|
|||||||
return default;
|
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)
|
public static string? SerializeJson<T>(T? value)
|
||||||
{
|
{
|
||||||
if (value == null)
|
if (value is null)
|
||||||
{
|
{
|
||||||
return 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 List<string> DeserializeStringList(string? json) => JsonStringArrayHelper.Deserialize(json);
|
||||||
|
|
||||||
public static string? GetOrderStatusName(string statusCode)
|
public static string? GetOrderStatusName(string statusCode)
|
||||||
|
|||||||
@ -17,11 +17,11 @@ public static class OmsFileUploadHelper
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 保存充值截图,返回以 /uploads 开头的相对访问路径。
|
/// 保存充值截图,返回以 /uploads 开头的相对访问路径。
|
||||||
/// </summary>
|
/// </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)
|
if (file == null || file.Length <= 0)
|
||||||
{
|
{
|
||||||
throw new BusinessException("请上传充值截图");
|
return "0";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (file.Length > MaxImageBytes)
|
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);
|
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]
|
[Fact]
|
||||||
public void DeserializeStringList_ShouldReturnEmptyListWhenJsonMissing()
|
public void DeserializeStringList_ShouldReturnEmptyListWhenJsonMissing()
|
||||||
{
|
{
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user