fix: 修复门店租户写入与跨租户查看
This commit is contained in:
@@ -103,6 +103,7 @@
|
|||||||
"merchant_category:delete",
|
"merchant_category:delete",
|
||||||
"store:create",
|
"store:create",
|
||||||
"store:read",
|
"store:read",
|
||||||
|
"store:read:all",
|
||||||
"store:update",
|
"store:update",
|
||||||
"store:delete",
|
"store:delete",
|
||||||
"store-table-area:read",
|
"store-table-area:read",
|
||||||
@@ -411,6 +412,7 @@
|
|||||||
"merchant_category:delete",
|
"merchant_category:delete",
|
||||||
"store:create",
|
"store:create",
|
||||||
"store:read",
|
"store:read",
|
||||||
|
"store:read:all",
|
||||||
"store:update",
|
"store:update",
|
||||||
"store:delete",
|
"store:delete",
|
||||||
"product:create",
|
"product:create",
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using MediatR;
|
using MediatR;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
using TakeoutSaaS.Application.App.Stores.Dto;
|
using TakeoutSaaS.Application.App.Stores.Dto;
|
||||||
using TakeoutSaaS.Application.App.Stores.Queries;
|
using TakeoutSaaS.Application.App.Stores.Queries;
|
||||||
using TakeoutSaaS.Application.App.Stores.Services;
|
using TakeoutSaaS.Application.App.Stores.Services;
|
||||||
@@ -17,6 +18,7 @@ namespace TakeoutSaaS.Application.App.Stores.Handlers;
|
|||||||
public sealed class CalculateStoreFeeQueryHandler(
|
public sealed class CalculateStoreFeeQueryHandler(
|
||||||
IStoreRepository storeRepository,
|
IStoreRepository storeRepository,
|
||||||
ITenantProvider tenantProvider,
|
ITenantProvider tenantProvider,
|
||||||
|
IHttpContextAccessor httpContextAccessor,
|
||||||
IStoreFeeCalculationService feeCalculationService)
|
IStoreFeeCalculationService feeCalculationService)
|
||||||
: IRequestHandler<CalculateStoreFeeQuery, StoreFeeCalculationResultDto>
|
: IRequestHandler<CalculateStoreFeeQuery, StoreFeeCalculationResultDto>
|
||||||
{
|
{
|
||||||
@@ -24,7 +26,8 @@ public sealed class CalculateStoreFeeQueryHandler(
|
|||||||
public async Task<StoreFeeCalculationResultDto> Handle(CalculateStoreFeeQuery request, CancellationToken cancellationToken)
|
public async Task<StoreFeeCalculationResultDto> Handle(CalculateStoreFeeQuery request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// 1. 校验门店存在
|
// 1. 校验门店存在
|
||||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
var ignoreTenantFilter = StoreTenantAccess.ShouldIgnoreTenantFilter(httpContextAccessor);
|
||||||
|
var tenantId = ignoreTenantFilter ? 0 : tenantProvider.GetCurrentTenantId();
|
||||||
var store = await storeRepository.FindByIdAsync(request.StoreId, tenantId, cancellationToken);
|
var store = await storeRepository.FindByIdAsync(request.StoreId, tenantId, cancellationToken);
|
||||||
if (store is null)
|
if (store is null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using MediatR;
|
using MediatR;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
using TakeoutSaaS.Application.App.Stores.Dto;
|
using TakeoutSaaS.Application.App.Stores.Dto;
|
||||||
using TakeoutSaaS.Application.App.Stores.Queries;
|
using TakeoutSaaS.Application.App.Stores.Queries;
|
||||||
using TakeoutSaaS.Domain.Stores.Enums;
|
using TakeoutSaaS.Domain.Stores.Enums;
|
||||||
@@ -14,14 +15,16 @@ namespace TakeoutSaaS.Application.App.Stores.Handlers;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class CheckStoreQualificationsQueryHandler(
|
public sealed class CheckStoreQualificationsQueryHandler(
|
||||||
IStoreRepository storeRepository,
|
IStoreRepository storeRepository,
|
||||||
ITenantProvider tenantProvider)
|
ITenantProvider tenantProvider,
|
||||||
|
IHttpContextAccessor httpContextAccessor)
|
||||||
: IRequestHandler<CheckStoreQualificationsQuery, StoreQualificationCheckResultDto>
|
: IRequestHandler<CheckStoreQualificationsQuery, StoreQualificationCheckResultDto>
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<StoreQualificationCheckResultDto> Handle(CheckStoreQualificationsQuery request, CancellationToken cancellationToken)
|
public async Task<StoreQualificationCheckResultDto> Handle(CheckStoreQualificationsQuery request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// 1. 校验门店存在
|
// 1. 校验门店存在
|
||||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
var ignoreTenantFilter = StoreTenantAccess.ShouldIgnoreTenantFilter(httpContextAccessor);
|
||||||
|
var tenantId = ignoreTenantFilter ? 0 : tenantProvider.GetCurrentTenantId();
|
||||||
var store = await storeRepository.FindByIdAsync(request.StoreId, tenantId, cancellationToken);
|
var store = await storeRepository.FindByIdAsync(request.StoreId, tenantId, cancellationToken);
|
||||||
if (store is null)
|
if (store is null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
using MediatR;
|
using MediatR;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using TakeoutSaaS.Application.App.Stores;
|
using TakeoutSaaS.Application.App.Stores;
|
||||||
using TakeoutSaaS.Application.App.Stores.Commands;
|
using TakeoutSaaS.Application.App.Stores.Commands;
|
||||||
using TakeoutSaaS.Application.App.Stores.Dto;
|
using TakeoutSaaS.Application.App.Stores.Dto;
|
||||||
|
using TakeoutSaaS.Domain.Merchants.Repositories;
|
||||||
using TakeoutSaaS.Domain.Stores.Entities;
|
using TakeoutSaaS.Domain.Stores.Entities;
|
||||||
using TakeoutSaaS.Domain.Stores.Enums;
|
using TakeoutSaaS.Domain.Stores.Enums;
|
||||||
using TakeoutSaaS.Domain.Stores.Repositories;
|
using TakeoutSaaS.Domain.Stores.Repositories;
|
||||||
@@ -17,17 +19,30 @@ namespace TakeoutSaaS.Application.App.Stores.Handlers;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class CreateStoreCommandHandler(
|
public sealed class CreateStoreCommandHandler(
|
||||||
IStoreRepository storeRepository,
|
IStoreRepository storeRepository,
|
||||||
|
IMerchantRepository merchantRepository,
|
||||||
ITenantProvider tenantProvider,
|
ITenantProvider tenantProvider,
|
||||||
|
IHttpContextAccessor httpContextAccessor,
|
||||||
ILogger<CreateStoreCommandHandler> logger)
|
ILogger<CreateStoreCommandHandler> logger)
|
||||||
: IRequestHandler<CreateStoreCommand, StoreDto>
|
: IRequestHandler<CreateStoreCommand, StoreDto>
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<StoreDto> Handle(CreateStoreCommand request, CancellationToken cancellationToken)
|
public async Task<StoreDto> Handle(CreateStoreCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// 1. 校验门店坐标唯一性(100 米内禁止重复)
|
// 1. 校验商户存在并解析租户
|
||||||
|
var currentTenantId = tenantProvider.GetCurrentTenantId();
|
||||||
|
var allowCrossTenant = StoreTenantAccess.ShouldIgnoreTenantFilter(httpContextAccessor);
|
||||||
|
var merchant = allowCrossTenant
|
||||||
|
? await merchantRepository.FindByIdAsync(request.MerchantId, cancellationToken)
|
||||||
|
: await merchantRepository.FindByIdAsync(request.MerchantId, currentTenantId, cancellationToken);
|
||||||
|
if (merchant == null)
|
||||||
|
{
|
||||||
|
throw new BusinessException(ErrorCodes.NotFound, "商户不存在");
|
||||||
|
}
|
||||||
|
var tenantId = merchant.TenantId;
|
||||||
|
|
||||||
|
// 2. (空行后) 校验门店坐标唯一性(100 米内禁止重复)
|
||||||
if (request.Longitude.HasValue && request.Latitude.HasValue)
|
if (request.Longitude.HasValue && request.Latitude.HasValue)
|
||||||
{
|
{
|
||||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
|
||||||
var isDuplicate = await storeRepository.ExistsStoreWithinDistanceAsync(
|
var isDuplicate = await storeRepository.ExistsStoreWithinDistanceAsync(
|
||||||
request.MerchantId,
|
request.MerchantId,
|
||||||
tenantId,
|
tenantId,
|
||||||
@@ -41,16 +56,17 @@ public sealed class CreateStoreCommandHandler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. (空行后) 计算审核与经营状态
|
// 3. (空行后) 计算审核与经营状态
|
||||||
var now = DateTime.UtcNow;
|
var now = DateTime.UtcNow;
|
||||||
var isSameEntity = request.OwnershipType == StoreOwnershipType.SameEntity;
|
var isSameEntity = request.OwnershipType == StoreOwnershipType.SameEntity;
|
||||||
var auditStatus = isSameEntity ? StoreAuditStatus.Activated : StoreAuditStatus.Draft;
|
var auditStatus = isSameEntity ? StoreAuditStatus.Activated : StoreAuditStatus.Draft;
|
||||||
var businessStatus = StoreBusinessStatus.Resting;
|
var businessStatus = StoreBusinessStatus.Resting;
|
||||||
DateTime? activatedAt = isSameEntity ? now : null;
|
DateTime? activatedAt = isSameEntity ? now : null;
|
||||||
|
|
||||||
// 3. (空行后) 构建实体
|
// 4. (空行后) 构建实体
|
||||||
var store = new Store
|
var store = new Store
|
||||||
{
|
{
|
||||||
|
TenantId = tenantId,
|
||||||
MerchantId = request.MerchantId,
|
MerchantId = request.MerchantId,
|
||||||
Code = request.Code.Trim(),
|
Code = request.Code.Trim(),
|
||||||
Name = request.Name.Trim(),
|
Name = request.Name.Trim(),
|
||||||
@@ -79,10 +95,14 @@ public sealed class CreateStoreCommandHandler(
|
|||||||
SupportsQueueing = request.SupportsQueueing
|
SupportsQueueing = request.SupportsQueueing
|
||||||
};
|
};
|
||||||
|
|
||||||
// 4. (空行后) 持久化并初始化费用配置
|
// 5. (空行后) 持久化门店以获取标识
|
||||||
await storeRepository.AddStoreAsync(store, cancellationToken);
|
await storeRepository.AddStoreAsync(store, cancellationToken);
|
||||||
|
await storeRepository.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
// 6. (空行后) 初始化费用配置
|
||||||
await storeRepository.AddStoreFeeAsync(new StoreFee
|
await storeRepository.AddStoreFeeAsync(new StoreFee
|
||||||
{
|
{
|
||||||
|
TenantId = tenantId,
|
||||||
StoreId = store.Id,
|
StoreId = store.Id,
|
||||||
MinimumOrderAmount = 0m,
|
MinimumOrderAmount = 0m,
|
||||||
BaseDeliveryFee = 0m,
|
BaseDeliveryFee = 0m,
|
||||||
@@ -92,7 +112,7 @@ public sealed class CreateStoreCommandHandler(
|
|||||||
await storeRepository.SaveChangesAsync(cancellationToken);
|
await storeRepository.SaveChangesAsync(cancellationToken);
|
||||||
logger.LogInformation("创建门店 {StoreId} - {StoreName}", store.Id, store.Name);
|
logger.LogInformation("创建门店 {StoreId} - {StoreName}", store.Id, store.Name);
|
||||||
|
|
||||||
// 5. (空行后) 返回 DTO
|
// 7. (空行后) 返回 DTO
|
||||||
return StoreMapping.ToDto(store);
|
return StoreMapping.ToDto(store);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using MediatR;
|
using MediatR;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
using TakeoutSaaS.Application.App.Stores;
|
using TakeoutSaaS.Application.App.Stores;
|
||||||
using TakeoutSaaS.Application.App.Stores.Dto;
|
using TakeoutSaaS.Application.App.Stores.Dto;
|
||||||
using TakeoutSaaS.Application.App.Stores.Queries;
|
using TakeoutSaaS.Application.App.Stores.Queries;
|
||||||
@@ -13,13 +14,15 @@ namespace TakeoutSaaS.Application.App.Stores.Handlers;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class GetStoreByIdQueryHandler(
|
public sealed class GetStoreByIdQueryHandler(
|
||||||
IStoreRepository storeRepository,
|
IStoreRepository storeRepository,
|
||||||
ITenantProvider tenantProvider)
|
ITenantProvider tenantProvider,
|
||||||
|
IHttpContextAccessor httpContextAccessor)
|
||||||
: IRequestHandler<GetStoreByIdQuery, StoreDto?>
|
: IRequestHandler<GetStoreByIdQuery, StoreDto?>
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<StoreDto?> Handle(GetStoreByIdQuery request, CancellationToken cancellationToken)
|
public async Task<StoreDto?> Handle(GetStoreByIdQuery request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
var ignoreTenantFilter = StoreTenantAccess.ShouldIgnoreTenantFilter(httpContextAccessor);
|
||||||
|
var tenantId = ignoreTenantFilter ? 0 : tenantProvider.GetCurrentTenantId();
|
||||||
var store = await storeRepository.FindByIdAsync(request.StoreId, tenantId, cancellationToken);
|
var store = await storeRepository.FindByIdAsync(request.StoreId, tenantId, cancellationToken);
|
||||||
return store == null ? null : StoreMapping.ToDto(store);
|
return store == null ? null : StoreMapping.ToDto(store);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using MediatR;
|
using MediatR;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
using TakeoutSaaS.Application.App.Stores.Dto;
|
using TakeoutSaaS.Application.App.Stores.Dto;
|
||||||
using TakeoutSaaS.Application.App.Stores.Queries;
|
using TakeoutSaaS.Application.App.Stores.Queries;
|
||||||
using TakeoutSaaS.Domain.Stores.Entities;
|
using TakeoutSaaS.Domain.Stores.Entities;
|
||||||
@@ -14,14 +15,16 @@ namespace TakeoutSaaS.Application.App.Stores.Handlers;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class GetStoreFeeQueryHandler(
|
public sealed class GetStoreFeeQueryHandler(
|
||||||
IStoreRepository storeRepository,
|
IStoreRepository storeRepository,
|
||||||
ITenantProvider tenantProvider)
|
ITenantProvider tenantProvider,
|
||||||
|
IHttpContextAccessor httpContextAccessor)
|
||||||
: IRequestHandler<GetStoreFeeQuery, StoreFeeDto?>
|
: IRequestHandler<GetStoreFeeQuery, StoreFeeDto?>
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<StoreFeeDto?> Handle(GetStoreFeeQuery request, CancellationToken cancellationToken)
|
public async Task<StoreFeeDto?> Handle(GetStoreFeeQuery request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// 1. 校验门店存在
|
// 1. 校验门店存在
|
||||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
var ignoreTenantFilter = StoreTenantAccess.ShouldIgnoreTenantFilter(httpContextAccessor);
|
||||||
|
var tenantId = ignoreTenantFilter ? 0 : tenantProvider.GetCurrentTenantId();
|
||||||
var store = await storeRepository.FindByIdAsync(request.StoreId, tenantId, cancellationToken);
|
var store = await storeRepository.FindByIdAsync(request.StoreId, tenantId, cancellationToken);
|
||||||
if (store is null)
|
if (store is null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
using TakeoutSaaS.Application.App.Stores.Dto;
|
using TakeoutSaaS.Application.App.Stores.Dto;
|
||||||
using TakeoutSaaS.Application.App.Stores.Queries;
|
using TakeoutSaaS.Application.App.Stores.Queries;
|
||||||
using TakeoutSaaS.Domain.Stores.Repositories;
|
using TakeoutSaaS.Domain.Stores.Repositories;
|
||||||
@@ -12,17 +13,20 @@ namespace TakeoutSaaS.Application.App.Stores.Handlers;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class ListStoreBusinessHoursQueryHandler(
|
public sealed class ListStoreBusinessHoursQueryHandler(
|
||||||
IStoreRepository storeRepository,
|
IStoreRepository storeRepository,
|
||||||
ITenantProvider tenantProvider)
|
ITenantProvider tenantProvider,
|
||||||
|
IHttpContextAccessor httpContextAccessor)
|
||||||
: IRequestHandler<ListStoreBusinessHoursQuery, IReadOnlyList<StoreBusinessHourDto>>
|
: IRequestHandler<ListStoreBusinessHoursQuery, IReadOnlyList<StoreBusinessHourDto>>
|
||||||
{
|
{
|
||||||
private readonly IStoreRepository _storeRepository = storeRepository;
|
private readonly IStoreRepository _storeRepository = storeRepository;
|
||||||
private readonly ITenantProvider _tenantProvider = tenantProvider;
|
private readonly ITenantProvider _tenantProvider = tenantProvider;
|
||||||
|
private readonly IHttpContextAccessor _httpContextAccessor = httpContextAccessor;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<IReadOnlyList<StoreBusinessHourDto>> Handle(ListStoreBusinessHoursQuery request, CancellationToken cancellationToken)
|
public async Task<IReadOnlyList<StoreBusinessHourDto>> Handle(ListStoreBusinessHoursQuery request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// 1. 查询时段列表
|
// 1. 查询时段列表
|
||||||
var tenantId = _tenantProvider.GetCurrentTenantId();
|
var ignoreTenantFilter = StoreTenantAccess.ShouldIgnoreTenantFilter(_httpContextAccessor);
|
||||||
|
var tenantId = ignoreTenantFilter ? 0 : _tenantProvider.GetCurrentTenantId();
|
||||||
var hours = await _storeRepository.GetBusinessHoursAsync(request.StoreId, tenantId, cancellationToken);
|
var hours = await _storeRepository.GetBusinessHoursAsync(request.StoreId, tenantId, cancellationToken);
|
||||||
|
|
||||||
// 2. 映射 DTO
|
// 2. 映射 DTO
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
using TakeoutSaaS.Application.App.Stores.Dto;
|
using TakeoutSaaS.Application.App.Stores.Dto;
|
||||||
using TakeoutSaaS.Application.App.Stores.Queries;
|
using TakeoutSaaS.Application.App.Stores.Queries;
|
||||||
using TakeoutSaaS.Domain.Stores.Repositories;
|
using TakeoutSaaS.Domain.Stores.Repositories;
|
||||||
@@ -12,17 +13,20 @@ namespace TakeoutSaaS.Application.App.Stores.Handlers;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class ListStoreDeliveryZonesQueryHandler(
|
public sealed class ListStoreDeliveryZonesQueryHandler(
|
||||||
IStoreRepository storeRepository,
|
IStoreRepository storeRepository,
|
||||||
ITenantProvider tenantProvider)
|
ITenantProvider tenantProvider,
|
||||||
|
IHttpContextAccessor httpContextAccessor)
|
||||||
: IRequestHandler<ListStoreDeliveryZonesQuery, IReadOnlyList<StoreDeliveryZoneDto>>
|
: IRequestHandler<ListStoreDeliveryZonesQuery, IReadOnlyList<StoreDeliveryZoneDto>>
|
||||||
{
|
{
|
||||||
private readonly IStoreRepository _storeRepository = storeRepository;
|
private readonly IStoreRepository _storeRepository = storeRepository;
|
||||||
private readonly ITenantProvider _tenantProvider = tenantProvider;
|
private readonly ITenantProvider _tenantProvider = tenantProvider;
|
||||||
|
private readonly IHttpContextAccessor _httpContextAccessor = httpContextAccessor;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<IReadOnlyList<StoreDeliveryZoneDto>> Handle(ListStoreDeliveryZonesQuery request, CancellationToken cancellationToken)
|
public async Task<IReadOnlyList<StoreDeliveryZoneDto>> Handle(ListStoreDeliveryZonesQuery request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// 1. 查询配送区域
|
// 1. 查询配送区域
|
||||||
var tenantId = _tenantProvider.GetCurrentTenantId();
|
var ignoreTenantFilter = StoreTenantAccess.ShouldIgnoreTenantFilter(_httpContextAccessor);
|
||||||
|
var tenantId = ignoreTenantFilter ? 0 : _tenantProvider.GetCurrentTenantId();
|
||||||
var zones = await _storeRepository.GetDeliveryZonesAsync(request.StoreId, tenantId, cancellationToken);
|
var zones = await _storeRepository.GetDeliveryZonesAsync(request.StoreId, tenantId, cancellationToken);
|
||||||
|
|
||||||
// 2. 映射 DTO
|
// 2. 映射 DTO
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using MediatR;
|
using MediatR;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
using TakeoutSaaS.Application.App.Stores.Dto;
|
using TakeoutSaaS.Application.App.Stores.Dto;
|
||||||
using TakeoutSaaS.Application.App.Stores.Queries;
|
using TakeoutSaaS.Application.App.Stores.Queries;
|
||||||
using TakeoutSaaS.Domain.Stores.Repositories;
|
using TakeoutSaaS.Domain.Stores.Repositories;
|
||||||
@@ -13,14 +14,16 @@ namespace TakeoutSaaS.Application.App.Stores.Handlers;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class ListStoreQualificationsQueryHandler(
|
public sealed class ListStoreQualificationsQueryHandler(
|
||||||
IStoreRepository storeRepository,
|
IStoreRepository storeRepository,
|
||||||
ITenantProvider tenantProvider)
|
ITenantProvider tenantProvider,
|
||||||
|
IHttpContextAccessor httpContextAccessor)
|
||||||
: IRequestHandler<ListStoreQualificationsQuery, IReadOnlyList<StoreQualificationDto>>
|
: IRequestHandler<ListStoreQualificationsQuery, IReadOnlyList<StoreQualificationDto>>
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<IReadOnlyList<StoreQualificationDto>> Handle(ListStoreQualificationsQuery request, CancellationToken cancellationToken)
|
public async Task<IReadOnlyList<StoreQualificationDto>> Handle(ListStoreQualificationsQuery request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// 1. 校验门店存在
|
// 1. 校验门店存在
|
||||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
var ignoreTenantFilter = StoreTenantAccess.ShouldIgnoreTenantFilter(httpContextAccessor);
|
||||||
|
var tenantId = ignoreTenantFilter ? 0 : tenantProvider.GetCurrentTenantId();
|
||||||
var store = await storeRepository.FindByIdAsync(request.StoreId, tenantId, cancellationToken);
|
var store = await storeRepository.FindByIdAsync(request.StoreId, tenantId, cancellationToken);
|
||||||
if (store is null)
|
if (store is null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using MediatR;
|
using MediatR;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
using TakeoutSaaS.Application.App.Stores;
|
using TakeoutSaaS.Application.App.Stores;
|
||||||
using TakeoutSaaS.Application.App.Stores.Dto;
|
using TakeoutSaaS.Application.App.Stores.Dto;
|
||||||
using TakeoutSaaS.Application.App.Stores.Queries;
|
using TakeoutSaaS.Application.App.Stores.Queries;
|
||||||
@@ -13,13 +14,15 @@ namespace TakeoutSaaS.Application.App.Stores.Handlers;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class SearchStoresQueryHandler(
|
public sealed class SearchStoresQueryHandler(
|
||||||
IStoreRepository storeRepository,
|
IStoreRepository storeRepository,
|
||||||
ITenantProvider tenantProvider)
|
ITenantProvider tenantProvider,
|
||||||
|
IHttpContextAccessor httpContextAccessor)
|
||||||
: IRequestHandler<SearchStoresQuery, PagedResult<StoreDto>>
|
: IRequestHandler<SearchStoresQuery, PagedResult<StoreDto>>
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<PagedResult<StoreDto>> Handle(SearchStoresQuery request, CancellationToken cancellationToken)
|
public async Task<PagedResult<StoreDto>> Handle(SearchStoresQuery request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
var ignoreTenantFilter = StoreTenantAccess.ShouldIgnoreTenantFilter(httpContextAccessor);
|
||||||
|
var tenantId = ignoreTenantFilter ? 0 : tenantProvider.GetCurrentTenantId();
|
||||||
var stores = await storeRepository.SearchAsync(
|
var stores = await storeRepository.SearchAsync(
|
||||||
tenantId,
|
tenantId,
|
||||||
request.MerchantId,
|
request.MerchantId,
|
||||||
@@ -28,6 +31,7 @@ public sealed class SearchStoresQueryHandler(
|
|||||||
request.BusinessStatus,
|
request.BusinessStatus,
|
||||||
request.OwnershipType,
|
request.OwnershipType,
|
||||||
request.Keyword,
|
request.Keyword,
|
||||||
|
ignoreTenantFilter,
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
|
|
||||||
var sorted = ApplySorting(stores, request.SortBy, request.SortDescending);
|
var sorted = ApplySorting(stores, request.SortBy, request.SortDescending);
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace TakeoutSaaS.Application.App.Stores;
|
||||||
|
|
||||||
|
internal static class StoreTenantAccess
|
||||||
|
{
|
||||||
|
private const string PermissionClaimType = "permission";
|
||||||
|
private const string ViewAllStoresPermission = "store:read:all";
|
||||||
|
private static readonly string[] PlatformRoleCodes =
|
||||||
|
{
|
||||||
|
"super-admin",
|
||||||
|
"SUPER_ADMIN",
|
||||||
|
"PlatformAdmin",
|
||||||
|
"platform-admin"
|
||||||
|
};
|
||||||
|
|
||||||
|
public static bool ShouldIgnoreTenantFilter(IHttpContextAccessor httpContextAccessor)
|
||||||
|
{
|
||||||
|
var httpContext = httpContextAccessor.HttpContext;
|
||||||
|
if (httpContext == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var user = httpContext.User;
|
||||||
|
if (user?.Identity?.IsAuthenticated != true)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PlatformRoleCodes.Any(user.IsInRole))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var permissions = user.FindAll(PermissionClaimType)
|
||||||
|
.Select(c => c.Value?.Trim())
|
||||||
|
.Where(value => !string.IsNullOrWhiteSpace(value))
|
||||||
|
.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
return permissions.Contains(ViewAllStoresPermission);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,6 +29,7 @@ public interface IStoreRepository
|
|||||||
StoreBusinessStatus? businessStatus,
|
StoreBusinessStatus? businessStatus,
|
||||||
StoreOwnershipType? ownershipType,
|
StoreOwnershipType? ownershipType,
|
||||||
string? keyword,
|
string? keyword,
|
||||||
|
bool ignoreTenantFilter = false,
|
||||||
CancellationToken cancellationToken = default);
|
CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -19,9 +19,19 @@ public sealed class EfStoreRepository(TakeoutAppDbContext context) : IStoreRepos
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Task<Store?> FindByIdAsync(long storeId, long tenantId, CancellationToken cancellationToken = default)
|
public Task<Store?> FindByIdAsync(long storeId, long tenantId, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
return context.Stores
|
var query = context.Stores.AsNoTracking();
|
||||||
.AsNoTracking()
|
if (tenantId <= 0)
|
||||||
.Where(x => x.TenantId == tenantId && x.Id == storeId)
|
{
|
||||||
|
query = query.IgnoreQueryFilters()
|
||||||
|
.Where(x => x.DeletedAt == null);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
query = query.Where(x => x.TenantId == tenantId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return query
|
||||||
|
.Where(x => x.Id == storeId)
|
||||||
.FirstOrDefaultAsync(cancellationToken);
|
.FirstOrDefaultAsync(cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,11 +54,19 @@ public sealed class EfStoreRepository(TakeoutAppDbContext context) : IStoreRepos
|
|||||||
StoreBusinessStatus? businessStatus,
|
StoreBusinessStatus? businessStatus,
|
||||||
StoreOwnershipType? ownershipType,
|
StoreOwnershipType? ownershipType,
|
||||||
string? keyword,
|
string? keyword,
|
||||||
|
bool ignoreTenantFilter = false,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var query = context.Stores
|
var query = context.Stores.AsNoTracking();
|
||||||
.AsNoTracking()
|
if (ignoreTenantFilter)
|
||||||
.Where(x => x.TenantId == tenantId);
|
{
|
||||||
|
query = query.IgnoreQueryFilters()
|
||||||
|
.Where(x => x.DeletedAt == null);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
query = query.Where(x => x.TenantId == tenantId);
|
||||||
|
}
|
||||||
|
|
||||||
if (merchantId.HasValue)
|
if (merchantId.HasValue)
|
||||||
{
|
{
|
||||||
@@ -153,9 +171,19 @@ public sealed class EfStoreRepository(TakeoutAppDbContext context) : IStoreRepos
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<IReadOnlyList<StoreBusinessHour>> GetBusinessHoursAsync(long storeId, long tenantId, CancellationToken cancellationToken = default)
|
public async Task<IReadOnlyList<StoreBusinessHour>> GetBusinessHoursAsync(long storeId, long tenantId, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var hours = await context.StoreBusinessHours
|
var query = context.StoreBusinessHours.AsNoTracking();
|
||||||
.AsNoTracking()
|
if (tenantId <= 0)
|
||||||
.Where(x => x.TenantId == tenantId && x.StoreId == storeId)
|
{
|
||||||
|
query = query.IgnoreQueryFilters()
|
||||||
|
.Where(x => x.DeletedAt == null);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
query = query.Where(x => x.TenantId == tenantId);
|
||||||
|
}
|
||||||
|
|
||||||
|
var hours = await query
|
||||||
|
.Where(x => x.StoreId == storeId)
|
||||||
.OrderBy(x => x.DayOfWeek)
|
.OrderBy(x => x.DayOfWeek)
|
||||||
.ThenBy(x => x.StartTime)
|
.ThenBy(x => x.StartTime)
|
||||||
.ToListAsync(cancellationToken);
|
.ToListAsync(cancellationToken);
|
||||||
@@ -166,9 +194,19 @@ public sealed class EfStoreRepository(TakeoutAppDbContext context) : IStoreRepos
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Task<StoreFee?> GetStoreFeeAsync(long storeId, long tenantId, CancellationToken cancellationToken = default)
|
public Task<StoreFee?> GetStoreFeeAsync(long storeId, long tenantId, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
return context.StoreFees
|
var query = context.StoreFees.AsNoTracking();
|
||||||
.AsNoTracking()
|
if (tenantId <= 0)
|
||||||
.Where(x => x.TenantId == tenantId && x.StoreId == storeId)
|
{
|
||||||
|
query = query.IgnoreQueryFilters()
|
||||||
|
.Where(x => x.DeletedAt == null);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
query = query.Where(x => x.TenantId == tenantId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return query
|
||||||
|
.Where(x => x.StoreId == storeId)
|
||||||
.FirstOrDefaultAsync(cancellationToken);
|
.FirstOrDefaultAsync(cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,9 +226,19 @@ public sealed class EfStoreRepository(TakeoutAppDbContext context) : IStoreRepos
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<IReadOnlyList<StoreQualification>> GetQualificationsAsync(long storeId, long tenantId, CancellationToken cancellationToken = default)
|
public async Task<IReadOnlyList<StoreQualification>> GetQualificationsAsync(long storeId, long tenantId, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var qualifications = await context.StoreQualifications
|
var query = context.StoreQualifications.AsNoTracking();
|
||||||
.AsNoTracking()
|
if (tenantId <= 0)
|
||||||
.Where(x => x.TenantId == tenantId && x.StoreId == storeId)
|
{
|
||||||
|
query = query.IgnoreQueryFilters()
|
||||||
|
.Where(x => x.DeletedAt == null);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
query = query.Where(x => x.TenantId == tenantId);
|
||||||
|
}
|
||||||
|
|
||||||
|
var qualifications = await query
|
||||||
|
.Where(x => x.StoreId == storeId)
|
||||||
.OrderBy(x => x.SortOrder)
|
.OrderBy(x => x.SortOrder)
|
||||||
.ThenBy(x => x.QualificationType)
|
.ThenBy(x => x.QualificationType)
|
||||||
.ToListAsync(cancellationToken);
|
.ToListAsync(cancellationToken);
|
||||||
@@ -259,9 +307,19 @@ public sealed class EfStoreRepository(TakeoutAppDbContext context) : IStoreRepos
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<IReadOnlyList<StoreDeliveryZone>> GetDeliveryZonesAsync(long storeId, long tenantId, CancellationToken cancellationToken = default)
|
public async Task<IReadOnlyList<StoreDeliveryZone>> GetDeliveryZonesAsync(long storeId, long tenantId, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var zones = await context.StoreDeliveryZones
|
var query = context.StoreDeliveryZones.AsNoTracking();
|
||||||
.AsNoTracking()
|
if (tenantId <= 0)
|
||||||
.Where(x => x.TenantId == tenantId && x.StoreId == storeId)
|
{
|
||||||
|
query = query.IgnoreQueryFilters()
|
||||||
|
.Where(x => x.DeletedAt == null);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
query = query.Where(x => x.TenantId == tenantId);
|
||||||
|
}
|
||||||
|
|
||||||
|
var zones = await query
|
||||||
|
.Where(x => x.StoreId == storeId)
|
||||||
.OrderBy(x => x.SortOrder)
|
.OrderBy(x => x.SortOrder)
|
||||||
.ToListAsync(cancellationToken);
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user