1.05 #7

Merged
zzh merged 19 commits from test into master 2026-10-15 13:59:09 +08:00
18 changed files with 245 additions and 101 deletions
Showing only changes of commit b7be742046 - Show all commits

View File

@ -0,0 +1,24 @@
-- 销售产品分组字典:仓储代发(wms)。可重复执行。
-- 用于店铺订单「创建代发订单」选品过滤;销售产品 ProductGroup 填 wms。
SET @db := DATABASE();
-- 确保字典类型存在
INSERT INTO `t_dict_type` (`DictType`, `Remark`, `TypeSort`, `IsDeleted`, `CreateUserId`)
SELECT 'sales_product_group', '销售产品分组', 0, 0, 0
FROM DUAL
WHERE NOT EXISTS (
SELECT 1 FROM `t_dict_type`
WHERE `DictType` = 'sales_product_group' AND `IsDeleted` = 0
);
-- 写入 wms 字典项
INSERT INTO `t_dict` (`DictType`, `DictSort`, `DictCode`, `DictName`, `IsDeleted`, `CreateUserId`)
SELECT 'sales_product_group', 90, 'wms', '仓储代发', 0, 0
FROM DUAL
WHERE NOT EXISTS (
SELECT 1 FROM `t_dict`
WHERE `DictType` = 'sales_product_group'
AND `DictCode` = 'wms'
AND `IsDeleted` = 0
);

View File

@ -16,12 +16,12 @@ public class DataPermStore(FakeRbacDbContext dbContext, DataPermContextCache cac
PropertyNameCaseInsensitive = true
};
// EF Core 原始 SQL 参数不接受 DBNull,空值直接传 null
private static object? SqlParam(object? value) => value;
// EF Core 原始 SQL 参数不接受 DBNull;object[] 元素类型为 object,运行时仍可传 null
private static object SqlParam(object? value) => value ?? null!;
public async Task<UserDataPermDto> GetUserDataPermAsync(Guid userId, CancellationToken cancellationToken = default)
{
var user = await dbContext.Database.SqlQueryRaw<UserHeaderRow>($@"
var userSql = $@"
SELECT
u.`Id` AS `UserId`,
u.`Name` AS `UserName`,
@ -31,11 +31,13 @@ public class DataPermStore(FakeRbacDbContext dbContext, DataPermContextCache cac
LEFT JOIN `{FakeTables.Organization}` o ON o.`Id` = u.`OrganizationId` AND o.`IsDeleted` = 0
WHERE u.`Id` = {{0}} AND u.`IsDeleted` = 0
LIMIT 1
", userId.ToString()).FirstOrDefaultAsync(cancellationToken)
";
var user = await dbContext.Database.SqlQueryRaw<UserHeaderRow>(userSql, userId.ToString())
.FirstOrDefaultAsync(cancellationToken)
?? throw new BusinessException("用户不存在");
var now = DateTime.UtcNow;
var orgRows = await dbContext.Database.SqlQueryRaw<UserOrgPermRow>($@"
var orgSql = $@"
SELECT
p.`Id` AS `Id`,
p.`OrganizationId` AS `OrganizationId`,
@ -47,7 +49,9 @@ public class DataPermStore(FakeRbacDbContext dbContext, DataPermContextCache cac
FROM `user_org_perm` p
LEFT JOIN `{FakeTables.Organization}` o ON o.`Id` = p.`OrganizationId` AND o.`IsDeleted` = 0
WHERE p.`IsDeleted` = 0 AND p.`UserId` = {{0}}
", userId.ToString()).ToListAsync(cancellationToken);
";
var orgRows = await dbContext.Database.SqlQueryRaw<UserOrgPermRow>(orgSql, userId.ToString())
.ToListAsync(cancellationToken);
var scopeRows = await dbContext.Database.SqlQueryRaw<UserDataScopeRow>("""
SELECT p.`Id` AS `Id`, p.`DataScope` AS `DataScope`, p.`DataResourceCode` AS `DataResourceCode`, p.`ExpireTime` AS `ExpireTime`
@ -128,20 +132,24 @@ public class DataPermStore(FakeRbacDbContext dbContext, DataPermContextCache cac
public async Task<RoleDataPermDto> GetRoleDataPermAsync(Guid roleId, CancellationToken cancellationToken = default)
{
var role = await dbContext.Database.SqlQueryRaw<RoleHeaderRow>($@"
var roleSql = $@"
SELECT r.`Id` AS `RoleId`, r.`Name` AS `RoleName`
FROM `{FakeTables.Role}` r
WHERE r.`Id` = {{0}} AND r.`IsDeleted` = 0
LIMIT 1
", roleId.ToString()).FirstOrDefaultAsync(cancellationToken)
";
var role = await dbContext.Database.SqlQueryRaw<RoleHeaderRow>(roleSql, roleId.ToString())
.FirstOrDefaultAsync(cancellationToken)
?? throw new BusinessException("角色不存在");
var orgRows = await dbContext.Database.SqlQueryRaw<RoleOrgPermRow>($@"
var orgSql = $@"
SELECT p.`Id` AS `Id`, p.`OrganizationId` AS `OrganizationId`, o.`Name` AS `OrganizationName`, p.`OrgScope` AS `OrgScope`
FROM `role_org_perm` p
LEFT JOIN `{FakeTables.Organization}` o ON o.`Id` = p.`OrganizationId` AND o.`IsDeleted` = 0
WHERE p.`IsDeleted` = 0 AND p.`RoleId` = {{0}}
", roleId.ToString()).ToListAsync(cancellationToken);
";
var orgRows = await dbContext.Database.SqlQueryRaw<RoleOrgPermRow>(orgSql, roleId.ToString())
.ToListAsync(cancellationToken);
var scopeRows = await dbContext.Database.SqlQueryRaw<RoleDataScopeRow>("""
SELECT p.`Id` AS `Id`, p.`DataScope` AS `DataScope`, p.`DataResourceCode` AS `DataResourceCode`

View File

@ -11,6 +11,9 @@ public class PlatformStorePageInput
public int? CustomerId { get; set; }
/// <summary>
/// 平台编码:tiktok / temu / shopee / lazada;空或 all 表示全部。
/// </summary>
public string? PlatformCode { get; set; }
public string? StoreNickname { get; set; }
@ -147,11 +150,6 @@ public class PlatformStoreOrderListInput : PlatformStorePageInput
{
public int? StoreId { get; set; }
/// <summary>
/// 平台编码:tiktok / temu / shopee / lazada;空或 all 表示全部。
/// </summary>
public string? PlatformCode { get; set; }
/// <summary>
/// 履约状态:pending / toShip / shipped / canceled / suspended / completed;空或 all 表示全部。
/// </summary>

View File

@ -104,12 +104,12 @@ public class UserProfileRepository(FakeRbacDbContext dbContext) : IScopedDepende
sql,
[
userId.ToString(),
// EF Core 参数不接受 DBNull,空值直接传 null 即可映射为 SQL NULL
(object?)NormalizeOptional(profile.Phone),
(object?)NormalizeOptional(profile.EmployeeNo),
(object?)NormalizeOptional(profile.WechatId),
(object?)NormalizeOptional(profile.WechatQrCode),
(object?)NormalizeOptional(profile.WecomKfUrl),
// EF Core 参数不接受 DBNull;object[] 元素类型为 object,运行时仍可传 null
SqlParam(NormalizeOptional(profile.Phone)),
SqlParam(NormalizeOptional(profile.EmployeeNo)),
SqlParam(NormalizeOptional(profile.WechatId)),
SqlParam(NormalizeOptional(profile.WechatQrCode)),
SqlParam(NormalizeOptional(profile.WecomKfUrl)),
profile.IsEnabled ? 1 : 0,
],
cancellationToken);
@ -175,10 +175,10 @@ public class UserProfileRepository(FakeRbacDbContext dbContext) : IScopedDepende
sql,
[
userId.ToString(),
(object?)NormalizeOptional(profile.Phone),
(object?)NormalizeOptional(profile.WechatId),
(object?)NormalizeOptional(profile.WechatQrCode),
(object?)NormalizeOptional(profile.WecomKfUrl),
SqlParam(NormalizeOptional(profile.Phone)),
SqlParam(NormalizeOptional(profile.WechatId)),
SqlParam(NormalizeOptional(profile.WechatQrCode)),
SqlParam(NormalizeOptional(profile.WecomKfUrl)),
],
cancellationToken);
}
@ -255,6 +255,8 @@ public class UserProfileRepository(FakeRbacDbContext dbContext) : IScopedDepende
return value.Trim();
}
private static object SqlParam(object? value) => value ?? null!;
private sealed class UserProfileRow
{
public string? Phone { get; set; }

View File

@ -204,9 +204,9 @@ public class CustomerService : ApplicationService
// 同步 RBAC 登录邮箱,覆盖注册时的 @noemail.local 占位
try
{
await RbacDbContext.Database.ExecuteSqlRawAsync(
$"UPDATE `{FakeTables.User}` SET `Email` = {{0}} WHERE `userid` = {{1}} AND `IsDeleted` = 0",
email, customer.UserId);
var syncEmailSql =
$"UPDATE `{FakeTables.User}` SET `Email` = {{0}} WHERE `userid` = {{1}} AND `IsDeleted` = 0";
await RbacDbContext.Database.ExecuteSqlRawAsync(syncEmailSql, email, customer.UserId);
}
catch
{
@ -392,8 +392,8 @@ public class CustomerService : ApplicationService
private async Task<Guid?> GetRbacUserGuidByMapIdAsync(int mapId)
{
return await RbacDbContext.Database.SqlQueryRaw<Guid?>(
$"select `Id` as `Value` from `{FakeTables.User}` where `userid` = {{0}} and `IsDeleted` = 0", mapId)
var sql = $"select `Id` as `Value` from `{FakeTables.User}` where `userid` = {{0}} and `IsDeleted` = 0";
return await RbacDbContext.Database.SqlQueryRaw<Guid?>(sql, mapId)
.FirstOrDefaultAsync();
}
@ -442,7 +442,7 @@ public class CustomerService : ApplicationService
(string.IsNullOrWhiteSpace(keyword) ||
customer.CustomerCode.Contains(keyword) ||
customer.CustomerName.Contains(keyword) ||
customer.Phone.Contains(keyword) ||
(customer.Phone ?? string.Empty).Contains(keyword) ||
(customer.Email ?? string.Empty).Contains(keyword)) &&
(!hasStatusFilter || customer.Status == statusValue) &&
(!hasCustomerGroupFilter || customer.CustomerGroup == customerGroupValue) &&
@ -497,8 +497,8 @@ public class CustomerService : ApplicationService
private async Task<int> GetRbacUserMapIdOrThrowAsync(Guid id)
{
var userId = await RbacDbContext.Database.SqlQueryRaw<int?>(
$"select `userid` as `Value` from `{FakeTables.User}` where `Id` = {{0}}", id)
var sql = $"select `userid` as `Value` from `{FakeTables.User}` where `Id` = {{0}}";
var userId = await RbacDbContext.Database.SqlQueryRaw<int?>(sql, id)
.FirstOrDefaultAsync();
if (!userId.HasValue || userId.Value <= 0)
{

View File

@ -17,7 +17,7 @@ namespace Oms.Application.Services.OpenApi;
/// <summary>
/// 仓配对外开放 API(Swagger 文档分组:warehouseOpenApi)。
/// 鉴权:Authorization: Bearer {token}(账号密码换取,有效期 48 小时)。
/// 鉴权:Authorization: {token}(账号密码换取,有效期 48 小时;无需 Bearer 前缀)。
/// 与客户 HMAC 分组 openApi 相互独立。
/// </summary>
[ApiExplorerSettings(GroupName = "warehouseOpenApi")]
@ -45,7 +45,7 @@ public class WarehouseOpenApiService : ApplicationService
LazyServiceProvider.GetRequiredService<OutboundRecordService>();
/// <summary>
/// 获取 Token(有效期 48 小时)。后续请求 Header:Authorization: Bearer {token}。
/// 获取 Token(有效期 48 小时)。后续请求 Header:Authorization: {token}(无需 Bearer)。
/// </summary>
[AllowAnonymous]
[HttpPost("/openapi/warehouse/token")]
@ -75,7 +75,7 @@ public class WarehouseOpenApiService : ApplicationService
/// <summary>
/// 入库上传件重尺。与操作台「入库扫描」页「入库」按钮同源(POST /operation/inbound/create)。
/// 写入收货件重尺、入库记录,并触发入库计费/自动触发。重量默认 kg,长度默认 cm。
/// Header:Authorization: Bearer {token}。
/// Header:Authorization: {token}(无需 Bearer)。
/// </summary>
[Authorize]
[HttpPost("/openapi/warehouse/inbound")]
@ -86,7 +86,7 @@ public class WarehouseOpenApiService : ApplicationService
/// <summary>
/// 上传件重尺。mode:0=入库(同「入库」按钮),1=出库(同「出库」按钮)。
/// Header:Authorization: Bearer {token}。
/// Header:Authorization: {token}(无需 Bearer)。
/// </summary>
[Authorize]
[HttpPost("/openapi/warehouse/weight/upload")]
@ -164,7 +164,7 @@ public class WarehouseOpenApiService : ApplicationService
/// <summary>
/// 上传红外图片。Base64 解码后转存 COS,写入订单附件 fileType=infraredImage(Source=upload),
/// 与订单详情「附件」Tab →「红外图片」→「点击上传」(POST /order/files/upload)同源展示。
/// 成功无响应体。Header:Authorization: Bearer {token}。
/// 成功无响应体。Header:Authorization: {token}(无需 Bearer)。
/// </summary>
[Authorize]
[HttpPost("/openapi/warehouse/image/upload")]

View File

@ -525,12 +525,12 @@ internal static class ParcelBillingCalculator
priceSnapshot.Prices.Select(p => (p.Status, p.Details)));
private static IReadOnlyList<string> CollectDistinctDetailZoneKeys(
IEnumerable<(byte Status, List<ChannelPriceDetailOutput>? Details)> prices)
IEnumerable<(byte Status, List<ChannelPriceDetailOutput> Details)> prices)
{
var zones = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (var price in prices.Where(x => EnableStatusRules.IsEnabled(x.Status)))
{
foreach (var detail in price.Details ?? [])
foreach (var detail in price.Details)
{
var factors = ParseFactorDictionary(detail.FactorValuesJson);
if (TryGetZoneFactorText(factors, out var zone))

View File

@ -5,6 +5,7 @@ using Oms.Application.Dtos.Orders;
using Oms.Application.Dtos.PlatformStore;
using Oms.Application.Dtos.Shared;
using Oms.Application.Shared;
using Oms.Domain.ChannelsAggregate.Consts;
using Oms.Domain.ChannelsAggregate.Repositories;
using Oms.Domain.CustomersAggregate.Repositories;
using Oms.Domain.OrdersAggregate.Consts;
@ -409,7 +410,8 @@ public class PlatformStoreOrderService : ApplicationService
input.ProductCode,
input.WarehouseId,
input.Items,
requireBind: false);
requireBind: false,
requireWmsProductGroup: false);
}
/// <summary>
@ -429,7 +431,30 @@ public class PlatformStoreOrderService : ApplicationService
input.ProductCode,
input.WarehouseId,
input.Items,
requireBind: true);
requireBind: true,
requireWmsProductGroup: false);
}
/// <summary>
/// 批量创建仓储代发订单:与代发运单同链路(建单+绑定+产品计费),
/// 但产品必须属于分组 wms(仓储代发)。
/// </summary>
[HttpPost("/order/platform-store-order/batch-create-fulfill-wms")]
public async Task<PlatformStoreOrderBatchCreateLogisticsOutput> BatchCreateFulfillWmsAsync(
PlatformStoreOrderBatchCreateFulfillInput input)
{
if (input.WarehouseId <= 0)
{
throw new BusinessException("请指定发货仓库");
}
return await BatchCreateLogisticsInternalAsync(
input.CustomerId,
input.ProductCode,
input.WarehouseId,
input.Items,
requireBind: true,
requireWmsProductGroup: true);
}
private async Task<PlatformStoreOrderBatchCreateLogisticsOutput> BatchCreateLogisticsInternalAsync(
@ -437,7 +462,8 @@ public class PlatformStoreOrderService : ApplicationService
string? productCode,
int? warehouseId,
List<PlatformStoreOrderBatchCreateItemInput> items,
bool requireBind)
bool requireBind,
bool requireWmsProductGroup = false)
{
if (items == null || items.Count == 0)
{
@ -451,14 +477,14 @@ public class PlatformStoreOrderService : ApplicationService
if (string.IsNullOrWhiteSpace(productCode))
{
throw new BusinessException("请选择运输方式");
throw new BusinessException(requireWmsProductGroup ? "请选择仓储代发产品" : "请选择运输方式");
}
var customerId = await WarehouseCustomerHelper.ResolveRequiredCustomerIdAsync(
CustomerRepository,
CurrentUser,
customerIdInput);
var resolvedProductCode = await ResolveProductCodeRequiredAsync(productCode);
var resolvedProductCode = await ResolveProductCodeRequiredAsync(productCode, requireWmsProductGroup);
var batchNo = $"PSL{DateTime.Now:yyyyMMddHHmmss}";
var output = new PlatformStoreOrderBatchCreateLogisticsOutput { BatchNo = batchNo };
@ -488,7 +514,9 @@ public class PlatformStoreOrderService : ApplicationService
itemInput.Receiver,
requireBind ? itemInput.BindItems : null);
item.Success = true;
item.Message = requireBind
item.Message = requireWmsProductGroup
? $"已创建代发订单 {result.ServiceNo} 并绑定"
: requireBind
? $"已创建物流单 {result.ServiceNo} 并绑定"
: $"已创建物流单 {result.ServiceNo}";
item.LinkedOrderId = result.OrderId;
@ -523,7 +551,7 @@ public class PlatformStoreOrderService : ApplicationService
List<OrderStockBindSaveItemInput>? bindItems)
{
var order = await GetOrderOrThrowAsync(customerId, storeOrderId);
await EnsureMatchableAsync(order);
await EnsureCanCreateLogisticsAsync(order);
var lines = await StoreOrderLineRepository.GetListAsync(x => x.StoreOrderId == order.Id && !x.IsShipExcluded);
if (lines.Count == 0)
@ -639,7 +667,9 @@ public class PlatformStoreOrderService : ApplicationService
await OrderRepository.UpdateAsync(logisticsOrder);
}
private async Task<string> ResolveProductCodeRequiredAsync(string productCode)
private async Task<string> ResolveProductCodeRequiredAsync(
string productCode,
bool requireWmsProductGroup = false)
{
var normalized = productCode.Trim();
var product = await SalesProductRepository.GetByProductCodeAsync(normalized);
@ -648,6 +678,15 @@ public class PlatformStoreOrderService : ApplicationService
throw new BusinessException("销售产品不存在或未启用");
}
if (requireWmsProductGroup
&& !string.Equals(
product.ProductGroup?.Trim(),
SalesProductGroupCodes.Wms,
StringComparison.OrdinalIgnoreCase))
{
throw new BusinessException("仓储代发订单仅可选择产品分组为「仓储代发(wms)」的销售产品");
}
return normalized;
}
@ -734,6 +773,26 @@ public class PlatformStoreOrderService : ApplicationService
}
}
/// <summary>
/// 创建运单/代发:不限制履约状态;拦截搁置与未删除的已关联物流单。
/// </summary>
private async Task EnsureCanCreateLogisticsAsync(SalesStoreOrder order)
{
if (order.IsSuspended)
{
throw new BusinessException("订单已搁置,不能创建");
}
if (order.LinkedOrderId is > 0)
{
var linked = await OrderRepository.FirstOrDefaultAsync(x => x.Id == order.LinkedOrderId.Value);
if (linked != null && linked.OrderStatus != OrderStatusCodes.Deleted)
{
throw new BusinessException($"已关联小包订单 {linked.ServiceNo},不能重复创建");
}
}
}
private async Task EnsureMatchableAsync(SalesStoreOrder order)
{
if (order.IsSuspended)

View File

@ -277,7 +277,7 @@ public class NoticeService : ApplicationService
x.ParentCustomerId == null &&
(x.CustomerName.Contains(pattern) ||
x.CustomerCode.Contains(pattern) ||
x.Phone.Contains(pattern));
(x.Phone != null && x.Phone.Contains(pattern)));
}
var total = await CustomerRepository.CountAsync(predicate);

View File

@ -167,14 +167,14 @@ public static class OmsRbacRegistrationGuard
return;
}
var hit = await rbacDbContext.Database
.SqlQueryRaw<int?>(
$@"
var sql = $@"
SELECT 1 AS Value
FROM `{FakeTables.Organization}`
WHERE `Id` = {{0}} AND `IsDeleted` = 0
LIMIT 1
", organizationId.Value.ToString())
";
var hit = await rbacDbContext.Database
.SqlQueryRaw<int?>(sql, organizationId.Value.ToString())
.FirstOrDefaultAsync(cancellationToken);
if (!hit.HasValue)
@ -201,14 +201,14 @@ public static class OmsRbacRegistrationGuard
foreach (var roleId in ids)
{
var hit = await rbacDbContext.Database
.SqlQueryRaw<int?>(
$@"
var sql = $@"
SELECT 1 AS Value
FROM `{FakeTables.Role}`
WHERE `Id` = {{0}} AND `IsDeleted` = 0
LIMIT 1
", roleId.ToString())
";
var hit = await rbacDbContext.Database
.SqlQueryRaw<int?>(sql, roleId.ToString())
.FirstOrDefaultAsync(cancellationToken);
if (!hit.HasValue)
@ -492,20 +492,21 @@ public static class OmsRbacRegistrationGuard
// 唯一索引不按 IsDeleted 区分,软删除账号仍会触发 Duplicate entry。
if (excludeUserId is Guid id && id != Guid.Empty)
{
var sql = $"SELECT 1 AS Value FROM `{FakeTables.User}` WHERE `Account` = {{0}} AND `Id` <> {{1}} LIMIT 1";
var hit = await rbacDbContext.Database
.SqlQueryRaw<int?>(
$"SELECT 1 AS Value FROM `{FakeTables.User}` WHERE `Account` = {{0}} AND `Id` <> {{1}} LIMIT 1",
account, id.ToString())
.SqlQueryRaw<int?>(sql, account, id.ToString())
.FirstOrDefaultAsync(cancellationToken);
return hit.HasValue;
}
{
var sql = $"SELECT 1 AS Value FROM `{FakeTables.User}` WHERE `Account` = {{0}} LIMIT 1";
var any = await rbacDbContext.Database
.SqlQueryRaw<int?>(
$"SELECT 1 AS Value FROM `{FakeTables.User}` WHERE `Account` = {{0}} LIMIT 1", account)
.SqlQueryRaw<int?>(sql, account)
.FirstOrDefaultAsync(cancellationToken);
return any.HasValue;
}
}
private static async Task<bool> ExistsUserByEmailAsync(
FakeRbacDbContext rbacDbContext,
@ -515,20 +516,21 @@ public static class OmsRbacRegistrationGuard
{
if (excludeUserId is Guid id && id != Guid.Empty)
{
var sql = $"SELECT 1 AS Value FROM `{FakeTables.User}` WHERE `Email` = {{0}} AND `Id` <> {{1}} LIMIT 1";
var hit = await rbacDbContext.Database
.SqlQueryRaw<int?>(
$"SELECT 1 AS Value FROM `{FakeTables.User}` WHERE `Email` = {{0}} AND `Id` <> {{1}} LIMIT 1",
email, id.ToString())
.SqlQueryRaw<int?>(sql, email, id.ToString())
.FirstOrDefaultAsync(cancellationToken);
return hit.HasValue;
}
{
var sql = $"SELECT 1 AS Value FROM `{FakeTables.User}` WHERE `Email` = {{0}} LIMIT 1";
var any = await rbacDbContext.Database
.SqlQueryRaw<int?>(
$"SELECT 1 AS Value FROM `{FakeTables.User}` WHERE `Email` = {{0}} LIMIT 1", email)
.SqlQueryRaw<int?>(sql, email)
.FirstOrDefaultAsync(cancellationToken);
return any.HasValue;
}
}
private static async Task<bool> ExistsUserByPhoneAsync(
FakeRbacDbContext rbacDbContext,
@ -538,20 +540,21 @@ public static class OmsRbacRegistrationGuard
{
if (excludeUserId is Guid id && id != Guid.Empty)
{
var sql = $"SELECT 1 AS Value FROM `{FakeTables.User}` WHERE `Phone` = {{0}} AND `Id` <> {{1}} LIMIT 1";
var hit = await rbacDbContext.Database
.SqlQueryRaw<int?>(
$"SELECT 1 AS Value FROM `{FakeTables.User}` WHERE `Phone` = {{0}} AND `Id` <> {{1}} LIMIT 1",
phone, id.ToString())
.SqlQueryRaw<int?>(sql, phone, id.ToString())
.FirstOrDefaultAsync(cancellationToken);
return hit.HasValue;
}
{
var sql = $"SELECT 1 AS Value FROM `{FakeTables.User}` WHERE `Phone` = {{0}} LIMIT 1";
var any = await rbacDbContext.Database
.SqlQueryRaw<int?>(
$"SELECT 1 AS Value FROM `{FakeTables.User}` WHERE `Phone` = {{0}} LIMIT 1", phone)
.SqlQueryRaw<int?>(sql, phone)
.FirstOrDefaultAsync(cancellationToken);
return any.HasValue;
}
}
private static async Task<bool> ExistsUserByWechatIdAsync(
FakeRbacDbContext rbacDbContext,
@ -561,20 +564,21 @@ public static class OmsRbacRegistrationGuard
{
if (excludeUserId is Guid id && id != Guid.Empty)
{
var sql = $"SELECT 1 AS Value FROM `{FakeTables.User}` WHERE `WechatId` = {{0}} AND `Id` <> {{1}} LIMIT 1";
var hit = await rbacDbContext.Database
.SqlQueryRaw<int?>(
$"SELECT 1 AS Value FROM `{FakeTables.User}` WHERE `WechatId` = {{0}} AND `Id` <> {{1}} LIMIT 1",
wechatId, id.ToString())
.SqlQueryRaw<int?>(sql, wechatId, id.ToString())
.FirstOrDefaultAsync(cancellationToken);
return hit.HasValue;
}
{
var sql = $"SELECT 1 AS Value FROM `{FakeTables.User}` WHERE `WechatId` = {{0}} LIMIT 1";
var any = await rbacDbContext.Database
.SqlQueryRaw<int?>(
$"SELECT 1 AS Value FROM `{FakeTables.User}` WHERE `WechatId` = {{0}} LIMIT 1", wechatId)
.SqlQueryRaw<int?>(sql, wechatId)
.FirstOrDefaultAsync(cancellationToken);
return any.HasValue;
}
}
private static string ExtractDeepMessage(Exception ex)
{

View File

@ -0,0 +1,10 @@
namespace Oms.Domain.ChannelsAggregate.Consts;
/// <summary>
/// 销售产品分组编码(字典 sales_product_group.DictCode)。
/// </summary>
public static class SalesProductGroupCodes
{
/// <summary>仓储代发(贴单出库 / 仓内履约计费)。</summary>
public const string Wms = "wms";
}

View File

@ -43,7 +43,7 @@ public static class OrderFileTypeCodes
return false;
}
return !string.Equals(fileType.Trim(), DownstreamLabel, StringComparison.OrdinalIgnoreCase);
return !string.Equals(fileType!.Trim(), DownstreamLabel, StringComparison.OrdinalIgnoreCase);
}
public static string Normalize(string fileType) => fileType.Trim();

View File

@ -140,7 +140,7 @@ public class CustomerRepository : OmsSqlSugarRepository<Customer>, ICustomerRepo
query = query.Where((g, c) =>
c.CustomerCode.Contains(kw) ||
c.CustomerName.Contains(kw) ||
c.Phone.Contains(kw));
(c.Phone != null && c.Phone.Contains(kw)));
}
var total = await query.CountAsync(cancellationToken);

View File

@ -908,15 +908,15 @@ public partial class AuthService(
private async Task<Guid?> GetRbacUserGuidByMapIdAsync(int userId, CancellationToken cancellationToken)
{
return await rbacDbContext.Database.SqlQueryRaw<Guid?>(
$"select `Id` as `Value` from `{FakeTables.User}` where `IsDeleted` = 0 and `userid` = {{0}}", userId)
var sql = $"select `Id` as `Value` from `{FakeTables.User}` where `IsDeleted` = 0 and `userid` = {{0}}";
return await rbacDbContext.Database.SqlQueryRaw<Guid?>(sql, userId)
.FirstOrDefaultAsync(cancellationToken);
}
private async Task<int?> GetMapIdByUserGuidAsync(Guid userId, CancellationToken cancellationToken)
{
return await rbacDbContext.Database.SqlQueryRaw<int?>(
$"select `userid` as `Value` from `{FakeTables.User}` where `Id` = {{0}}", userId)
var sql = $"select `userid` as `Value` from `{FakeTables.User}` where `Id` = {{0}}";
return await rbacDbContext.Database.SqlQueryRaw<int?>(sql, userId)
.FirstOrDefaultAsync(cancellationToken);
}

View File

@ -0,0 +1,36 @@
namespace SimpleAdmin.Api.Auth;
/// <summary>
/// 仓配开放 API:Authorization 直接传 JWT(不要求 Bearer 前缀)。
/// 在进入 JwtBearer 前补上 Bearer,以便复用现有鉴权。
/// </summary>
public sealed class WarehouseOpenApiAuthHeaderMiddleware(RequestDelegate next)
{
public Task InvokeAsync(HttpContext context)
{
if (context.Request.Path.StartsWithSegments("/openapi/warehouse"))
{
NormalizeAuthorizationHeader(context.Request.Headers);
}
return next(context);
}
private static void NormalizeAuthorizationHeader(IHeaderDictionary headers)
{
var auth = headers.Authorization.ToString();
if (string.IsNullOrWhiteSpace(auth))
{
return;
}
auth = auth.Trim();
if (auth.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
{
return;
}
// 无 scheme:Authorization: {token}
headers.Authorization = "Bearer " + auth;
}
}

View File

@ -201,6 +201,9 @@ public class SimpleAdminApiModule : FakeModule
await next();
});
// 仓配开放 API:Authorization 直接传 token(可不带 Bearer)
app.UseMiddleware<WarehouseOpenApiAuthHeaderMiddleware>();
// 认证和授权
app.UseAuthentication();
app.UseAuthorization();

View File

@ -22,8 +22,8 @@ public class SimpleJwtService(IOptions<FakeJwtOptions> jwtOptions, UserService u
CancellationToken cancellationToken)
{
var claims = await base.GenerateClaimsByUserIdAsync(userId, cancellationToken);
var mapId = await rbacDbContext.Database.SqlQueryRaw<int?>(
$"select `userid` as `Value` from `{FakeTables.User}` where `Id` = {{0}}", userId)
var sql = $"select `userid` as `Value` from `{FakeTables.User}` where `Id` = {{0}}";
var mapId = await rbacDbContext.Database.SqlQueryRaw<int?>(sql, userId)
.FirstOrDefaultAsync(cancellationToken);
if (mapId.HasValue)
{

View File

@ -325,8 +325,8 @@ public class WeChatCustomerService(
private async Task<int> GetRbacUserMapIdOrThrowAsync(Guid id, CancellationToken cancellationToken)
{
var userId = await rbacDbContext.Database.SqlQueryRaw<int?>(
$"select `userid` as `Value` from `{FakeTables.User}` where `Id` = {{0}}", id)
var sql = $"select `userid` as `Value` from `{FakeTables.User}` where `Id` = {{0}}";
var userId = await rbacDbContext.Database.SqlQueryRaw<int?>(sql, id)
.FirstOrDefaultAsync(cancellationToken);
if (!userId.HasValue || userId.Value <= 0)
{