feat: 店铺订单支持仓储代发(wms)建单,放宽创建状态限制
All checks were successful
Build and Deploy / deploy (push) Successful in 4m34s
All checks were successful
Build and Deploy / deploy (push) Successful in 4m34s
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
e85dfb80b5
commit
b7be742046
24
sql/migrate_sales_product_group_wms.sql
Normal file
24
sql/migrate_sales_product_group_wms.sql
Normal 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
|
||||
);
|
||||
@ -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`
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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; }
|
||||
|
||||
@ -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)
|
||||
{
|
||||
|
||||
@ -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")]
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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,9 +514,11 @@ public class PlatformStoreOrderService : ApplicationService
|
||||
itemInput.Receiver,
|
||||
requireBind ? itemInput.BindItems : null);
|
||||
item.Success = true;
|
||||
item.Message = requireBind
|
||||
? $"已创建物流单 {result.ServiceNo} 并绑定"
|
||||
: $"已创建物流单 {result.ServiceNo}";
|
||||
item.Message = requireWmsProductGroup
|
||||
? $"已创建代发订单 {result.ServiceNo} 并绑定"
|
||||
: requireBind
|
||||
? $"已创建物流单 {result.ServiceNo} 并绑定"
|
||||
: $"已创建物流单 {result.ServiceNo}";
|
||||
item.LinkedOrderId = result.OrderId;
|
||||
item.ServiceNo = result.ServiceNo;
|
||||
output.SuccessCount++;
|
||||
@ -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)
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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,19 +492,20 @@ 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 any = await rbacDbContext.Database
|
||||
.SqlQueryRaw<int?>(
|
||||
$"SELECT 1 AS Value FROM `{FakeTables.User}` WHERE `Account` = {{0}} LIMIT 1", account)
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
return any.HasValue;
|
||||
{
|
||||
var sql = $"SELECT 1 AS Value FROM `{FakeTables.User}` WHERE `Account` = {{0}} LIMIT 1";
|
||||
var any = await rbacDbContext.Database
|
||||
.SqlQueryRaw<int?>(sql, account)
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
return any.HasValue;
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<bool> ExistsUserByEmailAsync(
|
||||
@ -515,19 +516,20 @@ 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 any = await rbacDbContext.Database
|
||||
.SqlQueryRaw<int?>(
|
||||
$"SELECT 1 AS Value FROM `{FakeTables.User}` WHERE `Email` = {{0}} LIMIT 1", email)
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
return any.HasValue;
|
||||
{
|
||||
var sql = $"SELECT 1 AS Value FROM `{FakeTables.User}` WHERE `Email` = {{0}} LIMIT 1";
|
||||
var any = await rbacDbContext.Database
|
||||
.SqlQueryRaw<int?>(sql, email)
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
return any.HasValue;
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<bool> ExistsUserByPhoneAsync(
|
||||
@ -538,19 +540,20 @@ 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 any = await rbacDbContext.Database
|
||||
.SqlQueryRaw<int?>(
|
||||
$"SELECT 1 AS Value FROM `{FakeTables.User}` WHERE `Phone` = {{0}} LIMIT 1", phone)
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
return any.HasValue;
|
||||
{
|
||||
var sql = $"SELECT 1 AS Value FROM `{FakeTables.User}` WHERE `Phone` = {{0}} LIMIT 1";
|
||||
var any = await rbacDbContext.Database
|
||||
.SqlQueryRaw<int?>(sql, phone)
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
return any.HasValue;
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<bool> ExistsUserByWechatIdAsync(
|
||||
@ -561,19 +564,20 @@ 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 any = await rbacDbContext.Database
|
||||
.SqlQueryRaw<int?>(
|
||||
$"SELECT 1 AS Value FROM `{FakeTables.User}` WHERE `WechatId` = {{0}} LIMIT 1", wechatId)
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
return any.HasValue;
|
||||
{
|
||||
var sql = $"SELECT 1 AS Value FROM `{FakeTables.User}` WHERE `WechatId` = {{0}} LIMIT 1";
|
||||
var any = await rbacDbContext.Database
|
||||
.SqlQueryRaw<int?>(sql, wechatId)
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
return any.HasValue;
|
||||
}
|
||||
}
|
||||
|
||||
private static string ExtractDeepMessage(Exception ex)
|
||||
|
||||
@ -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";
|
||||
}
|
||||
@ -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();
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -201,6 +201,9 @@ public class SimpleAdminApiModule : FakeModule
|
||||
await next();
|
||||
});
|
||||
|
||||
// 仓配开放 API:Authorization 直接传 token(可不带 Bearer)
|
||||
app.UseMiddleware<WarehouseOpenApiAuthHeaderMiddleware>();
|
||||
|
||||
// 认证和授权
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
@ -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)
|
||||
{
|
||||
|
||||
@ -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)
|
||||
{
|
||||
|
||||
Loading…
Reference in New Issue
Block a user