feat: 门店模块移除租户上下文依赖

This commit is contained in:
2026-01-29 14:27:01 +00:00
parent 010c2b7043
commit 71e5a9dc29
36 changed files with 280 additions and 209 deletions

View File

@@ -6,7 +6,6 @@ using TakeoutSaaS.Domain.Stores.Entities;
using TakeoutSaaS.Domain.Stores.Repositories;
using TakeoutSaaS.Shared.Abstractions.Constants;
using TakeoutSaaS.Shared.Abstractions.Exceptions;
using TakeoutSaaS.Shared.Abstractions.Tenancy;
namespace TakeoutSaaS.Application.App.Stores.Handlers;
@@ -15,28 +14,27 @@ namespace TakeoutSaaS.Application.App.Stores.Handlers;
/// </summary>
public sealed class CreateStoreBusinessHourCommandHandler(
IStoreRepository storeRepository,
ITenantProvider tenantProvider,
ILogger<CreateStoreBusinessHourCommandHandler> logger)
: IRequestHandler<CreateStoreBusinessHourCommand, StoreBusinessHourDto>
{
private readonly IStoreRepository _storeRepository = storeRepository;
private readonly ITenantProvider _tenantProvider = tenantProvider;
private readonly ILogger<CreateStoreBusinessHourCommandHandler> _logger = logger;
/// <inheritdoc />
public async Task<StoreBusinessHourDto> Handle(CreateStoreBusinessHourCommand request, CancellationToken cancellationToken)
{
// 1. 校验门店存在
var tenantId = _tenantProvider.GetCurrentTenantId();
var store = await _storeRepository.FindByIdAsync(request.StoreId, tenantId, cancellationToken);
var store = await _storeRepository.FindByIdAsync(request.StoreId, tenantId: null, cancellationToken);
if (store is null)
{
throw new BusinessException(ErrorCodes.NotFound, "门店不存在");
}
var tenantId = store.TenantId;
// 2. 构建实体
// 2. (空行后) 构建实体并写入租户
var hour = new StoreBusinessHour
{
TenantId = tenantId,
StoreId = request.StoreId,
DayOfWeek = request.DayOfWeek,
HourType = request.HourType,
@@ -46,12 +44,12 @@ public sealed class CreateStoreBusinessHourCommandHandler(
Notes = request.Notes?.Trim()
};
// 3. 持久化
// 3. (空行后) 持久化
await _storeRepository.AddBusinessHoursAsync(new[] { hour }, cancellationToken);
await _storeRepository.SaveChangesAsync(cancellationToken);
_logger.LogInformation("创建营业时段 {BusinessHourId} 对应门店 {StoreId}", hour.Id, request.StoreId);
// 4. 返回 DTO
// 4. (空行后) 返回 DTO
return StoreMapping.ToDto(hour);
}
}

View File

@@ -7,7 +7,6 @@ using TakeoutSaaS.Domain.Stores.Entities;
using TakeoutSaaS.Domain.Stores.Repositories;
using TakeoutSaaS.Shared.Abstractions.Constants;
using TakeoutSaaS.Shared.Abstractions.Exceptions;
using TakeoutSaaS.Shared.Abstractions.Tenancy;
namespace TakeoutSaaS.Application.App.Stores.Handlers;
@@ -17,7 +16,6 @@ namespace TakeoutSaaS.Application.App.Stores.Handlers;
public sealed class CreateStoreEmployeeShiftCommandHandler(
IStoreRepository storeRepository,
IMerchantRepository merchantRepository,
ITenantProvider tenantProvider,
ILogger<CreateStoreEmployeeShiftCommandHandler> logger)
: IRequestHandler<CreateStoreEmployeeShiftCommand, StoreEmployeeShiftDto>
{
@@ -25,21 +23,21 @@ public sealed class CreateStoreEmployeeShiftCommandHandler(
public async Task<StoreEmployeeShiftDto> Handle(CreateStoreEmployeeShiftCommand request, CancellationToken cancellationToken)
{
// 1. 校验门店存在
var tenantId = tenantProvider.GetCurrentTenantId();
var store = await storeRepository.FindByIdAsync(request.StoreId, tenantId, cancellationToken);
var store = await storeRepository.FindByIdAsync(request.StoreId, tenantId: null, cancellationToken);
if (store is null)
{
throw new BusinessException(ErrorCodes.NotFound, "门店不存在");
}
var tenantId = store.TenantId;
// 2. 校验员工归属与状态
// 2. (空行后) 校验员工归属与状态
var staff = await merchantRepository.FindStaffByIdAsync(request.StaffId, tenantId, cancellationToken);
if (staff is null || (staff.StoreId.HasValue && staff.StoreId != request.StoreId))
{
throw new BusinessException(ErrorCodes.ValidationFailed, "员工不存在或不属于该门店");
}
// 3. 校验日期与冲突
// 3. (空行后) 校验日期与冲突
var from = request.ShiftDate.Date;
var to = request.ShiftDate.Date;
var shifts = await storeRepository.GetShiftsAsync(request.StoreId, tenantId, from, to, cancellationToken);
@@ -49,9 +47,10 @@ public sealed class CreateStoreEmployeeShiftCommandHandler(
throw new BusinessException(ErrorCodes.Conflict, "该员工当日已存在排班");
}
// 4. 构建实体
// 4. (空行后) 构建实体并写入租户
var shift = new StoreEmployeeShift
{
TenantId = tenantId,
StoreId = request.StoreId,
StaffId = request.StaffId,
ShiftDate = request.ShiftDate.Date,
@@ -61,12 +60,12 @@ public sealed class CreateStoreEmployeeShiftCommandHandler(
Notes = request.Notes?.Trim()
};
// 5. 持久化
// 5. (空行后) 持久化
await storeRepository.AddShiftsAsync(new[] { shift }, cancellationToken);
await storeRepository.SaveChangesAsync(cancellationToken);
logger.LogInformation("创建排班 {ShiftId} 员工 {StaffId} 门店 {StoreId}", shift.Id, shift.StaffId, shift.StoreId);
// 6. 返回 DTO
// 6. (空行后) 返回 DTO
return StoreMapping.ToDto(shift);
}
}

View File

@@ -6,7 +6,6 @@ using TakeoutSaaS.Domain.Stores.Entities;
using TakeoutSaaS.Domain.Stores.Repositories;
using TakeoutSaaS.Shared.Abstractions.Constants;
using TakeoutSaaS.Shared.Abstractions.Exceptions;
using TakeoutSaaS.Shared.Abstractions.Tenancy;
namespace TakeoutSaaS.Application.App.Stores.Handlers;
@@ -15,7 +14,6 @@ namespace TakeoutSaaS.Application.App.Stores.Handlers;
/// </summary>
public sealed class CreateStorePickupSlotCommandHandler(
IStoreRepository storeRepository,
ITenantProvider tenantProvider,
ILogger<CreateStorePickupSlotCommandHandler> logger)
: IRequestHandler<CreateStorePickupSlotCommand, StorePickupSlotDto>
{
@@ -23,14 +21,14 @@ public sealed class CreateStorePickupSlotCommandHandler(
public async Task<StorePickupSlotDto> Handle(CreateStorePickupSlotCommand request, CancellationToken cancellationToken)
{
// 1. 校验门店
var tenantId = tenantProvider.GetCurrentTenantId();
var store = await storeRepository.FindByIdAsync(request.StoreId, tenantId, cancellationToken);
var store = await storeRepository.FindByIdAsync(request.StoreId, tenantId: null, cancellationToken);
if (store is null)
{
throw new BusinessException(ErrorCodes.NotFound, "门店不存在");
}
var tenantId = store.TenantId;
// 2. 新建档期
// 2. (空行后) 新建档期
var slot = new StorePickupSlot
{
TenantId = tenantId,

View File

@@ -7,7 +7,6 @@ using TakeoutSaaS.Domain.Stores.Enums;
using TakeoutSaaS.Domain.Stores.Repositories;
using TakeoutSaaS.Shared.Abstractions.Constants;
using TakeoutSaaS.Shared.Abstractions.Exceptions;
using TakeoutSaaS.Shared.Abstractions.Tenancy;
namespace TakeoutSaaS.Application.App.Stores.Handlers;
@@ -16,7 +15,6 @@ namespace TakeoutSaaS.Application.App.Stores.Handlers;
/// </summary>
public sealed class CreateStoreQualificationCommandHandler(
IStoreRepository storeRepository,
ITenantProvider tenantProvider,
ILogger<CreateStoreQualificationCommandHandler> logger)
: IRequestHandler<CreateStoreQualificationCommand, StoreQualificationDto>
{
@@ -24,12 +22,12 @@ public sealed class CreateStoreQualificationCommandHandler(
public async Task<StoreQualificationDto> Handle(CreateStoreQualificationCommand request, CancellationToken cancellationToken)
{
// 1. 校验门店存在
var tenantId = tenantProvider.GetCurrentTenantId();
var store = await storeRepository.FindByIdAsync(request.StoreId, tenantId, cancellationToken);
var store = await storeRepository.FindByIdAsync(request.StoreId, tenantId: null, cancellationToken);
if (store is null)
{
throw new BusinessException(ErrorCodes.NotFound, "门店不存在");
}
var tenantId = store.TenantId;
// 2. (空行后) 审核中门店禁止修改资质
if (store.AuditStatus == StoreAuditStatus.Pending)
@@ -49,6 +47,7 @@ public sealed class CreateStoreQualificationCommandHandler(
{
existing = new StoreQualification
{
TenantId = tenantId,
StoreId = request.StoreId,
QualificationType = request.QualificationType,
FileUrl = request.FileUrl.Trim(),

View File

@@ -8,7 +8,6 @@ using TakeoutSaaS.Domain.Merchants.Repositories;
using TakeoutSaaS.Domain.Stores.Repositories;
using TakeoutSaaS.Shared.Abstractions.Constants;
using TakeoutSaaS.Shared.Abstractions.Exceptions;
using TakeoutSaaS.Shared.Abstractions.Tenancy;
namespace TakeoutSaaS.Application.App.Stores.Handlers;
@@ -18,7 +17,6 @@ namespace TakeoutSaaS.Application.App.Stores.Handlers;
public sealed class CreateStoreStaffCommandHandler(
IMerchantRepository merchantRepository,
IStoreRepository storeRepository,
ITenantProvider tenantProvider,
ILogger<CreateStoreStaffCommandHandler> logger)
: IRequestHandler<CreateStoreStaffCommand, StoreStaffDto>
{
@@ -26,16 +24,17 @@ public sealed class CreateStoreStaffCommandHandler(
public async Task<StoreStaffDto> Handle(CreateStoreStaffCommand request, CancellationToken cancellationToken)
{
// 1. 校验门店
var tenantId = tenantProvider.GetCurrentTenantId();
var store = await storeRepository.FindByIdAsync(request.StoreId, tenantId, cancellationToken);
var store = await storeRepository.FindByIdAsync(request.StoreId, tenantId: null, cancellationToken);
if (store is null)
{
throw new BusinessException(ErrorCodes.NotFound, "门店不存在");
}
var tenantId = store.TenantId;
// 2. 组装员工
// 2. (空行后) 组装员工并写入租户
var staff = new MerchantStaff
{
TenantId = tenantId,
MerchantId = store.MerchantId,
StoreId = request.StoreId,
Name = request.Name.Trim(),

View File

@@ -6,7 +6,6 @@ using TakeoutSaaS.Domain.Stores.Entities;
using TakeoutSaaS.Domain.Stores.Repositories;
using TakeoutSaaS.Shared.Abstractions.Constants;
using TakeoutSaaS.Shared.Abstractions.Exceptions;
using TakeoutSaaS.Shared.Abstractions.Tenancy;
namespace TakeoutSaaS.Application.App.Stores.Handlers;
@@ -15,7 +14,6 @@ namespace TakeoutSaaS.Application.App.Stores.Handlers;
/// </summary>
public sealed class CreateStoreTableAreaCommandHandler(
IStoreRepository storeRepository,
ITenantProvider tenantProvider,
ILogger<CreateStoreTableAreaCommandHandler> logger)
: IRequestHandler<CreateStoreTableAreaCommand, StoreTableAreaDto>
{
@@ -23,14 +21,14 @@ public sealed class CreateStoreTableAreaCommandHandler(
public async Task<StoreTableAreaDto> Handle(CreateStoreTableAreaCommand request, CancellationToken cancellationToken)
{
// 1. 校验门店存在
var tenantId = tenantProvider.GetCurrentTenantId();
var store = await storeRepository.FindByIdAsync(request.StoreId, tenantId, cancellationToken);
var store = await storeRepository.FindByIdAsync(request.StoreId, tenantId: null, cancellationToken);
if (store is null)
{
throw new BusinessException(ErrorCodes.NotFound, "门店不存在");
}
var tenantId = store.TenantId;
// 2. 校验区域名称唯一
// 2. (空行后) 校验区域名称唯一
var existingAreas = await storeRepository.GetTableAreasAsync(request.StoreId, tenantId, cancellationToken);
var hasDuplicate = existingAreas.Any(x => x.Name.Equals(request.Name, StringComparison.OrdinalIgnoreCase));
if (hasDuplicate)
@@ -38,21 +36,22 @@ public sealed class CreateStoreTableAreaCommandHandler(
throw new BusinessException(ErrorCodes.Conflict, "区域名称已存在");
}
// 3. 构建实体
// 3. (空行后) 构建实体并写入租户
var area = new StoreTableArea
{
TenantId = tenantId,
StoreId = request.StoreId,
Name = request.Name.Trim(),
Description = request.Description?.Trim(),
SortOrder = request.SortOrder
};
// 4. 持久化
// 4. (空行后) 持久化
await storeRepository.AddTableAreasAsync(new[] { area }, cancellationToken);
await storeRepository.SaveChangesAsync(cancellationToken);
logger.LogInformation("创建桌台区域 {AreaId} 对应门店 {StoreId}", area.Id, request.StoreId);
// 5. 返回 DTO
// 5. (空行后) 返回 DTO
return StoreMapping.ToDto(area);
}
}

View File

@@ -2,7 +2,6 @@ using MediatR;
using Microsoft.Extensions.Logging;
using TakeoutSaaS.Application.App.Stores.Commands;
using TakeoutSaaS.Domain.Stores.Repositories;
using TakeoutSaaS.Shared.Abstractions.Tenancy;
namespace TakeoutSaaS.Application.App.Stores.Handlers;
@@ -11,32 +10,37 @@ namespace TakeoutSaaS.Application.App.Stores.Handlers;
/// </summary>
public sealed class DeleteStoreBusinessHourCommandHandler(
IStoreRepository storeRepository,
ITenantProvider tenantProvider,
ILogger<DeleteStoreBusinessHourCommandHandler> logger)
: IRequestHandler<DeleteStoreBusinessHourCommand, bool>
{
private readonly IStoreRepository _storeRepository = storeRepository;
private readonly ITenantProvider _tenantProvider = tenantProvider;
private readonly ILogger<DeleteStoreBusinessHourCommandHandler> _logger = logger;
/// <inheritdoc />
public async Task<bool> Handle(DeleteStoreBusinessHourCommand request, CancellationToken cancellationToken)
{
// 1. 读取时段
var tenantId = _tenantProvider.GetCurrentTenantId();
// 1. 读取门店并解析租户
var store = await _storeRepository.FindByIdAsync(request.StoreId, tenantId: null, cancellationToken);
if (store is null)
{
return false;
}
var tenantId = store.TenantId;
// 2. (空行后) 读取时段
var existing = await _storeRepository.FindBusinessHourByIdAsync(request.BusinessHourId, tenantId, cancellationToken);
if (existing is null)
{
return false;
}
// 2. 校验门店归属
// 3. (空行后) 校验门店归属
if (existing.StoreId != request.StoreId)
{
return false;
}
// 3. 删除
// 4. (空行后) 删除
await _storeRepository.DeleteBusinessHourAsync(request.BusinessHourId, tenantId, cancellationToken);
await _storeRepository.SaveChangesAsync(cancellationToken);
_logger.LogInformation("删除营业时段 {BusinessHourId} 对应门店 {StoreId}", request.BusinessHourId, request.StoreId);

View File

@@ -2,7 +2,6 @@ using MediatR;
using Microsoft.Extensions.Logging;
using TakeoutSaaS.Application.App.Stores.Commands;
using TakeoutSaaS.Domain.Stores.Repositories;
using TakeoutSaaS.Shared.Abstractions.Tenancy;
namespace TakeoutSaaS.Application.App.Stores.Handlers;
@@ -11,26 +10,24 @@ namespace TakeoutSaaS.Application.App.Stores.Handlers;
/// </summary>
public sealed class DeleteStoreCommandHandler(
IStoreRepository storeRepository,
ITenantProvider tenantProvider,
ILogger<DeleteStoreCommandHandler> logger)
: IRequestHandler<DeleteStoreCommand, bool>
{
private readonly IStoreRepository _storeRepository = storeRepository;
private readonly ITenantProvider _tenantProvider = tenantProvider;
private readonly ILogger<DeleteStoreCommandHandler> _logger = logger;
/// <inheritdoc />
public async Task<bool> Handle(DeleteStoreCommand request, CancellationToken cancellationToken)
{
// 1. 校验存在性
var tenantId = _tenantProvider.GetCurrentTenantId();
var existing = await _storeRepository.FindByIdAsync(request.StoreId, tenantId, cancellationToken);
// 1. 校验存在性(跨租户)
var existing = await _storeRepository.FindByIdAsync(request.StoreId, tenantId: null, cancellationToken);
if (existing == null)
{
return false;
}
var tenantId = existing.TenantId;
// 2. 删除
// 2. (空行后) 删除
await _storeRepository.DeleteStoreAsync(request.StoreId, tenantId, cancellationToken);
await _storeRepository.SaveChangesAsync(cancellationToken);
_logger.LogInformation("删除门店 {StoreId}", request.StoreId);

View File

@@ -2,7 +2,6 @@ using MediatR;
using Microsoft.Extensions.Logging;
using TakeoutSaaS.Application.App.Stores.Commands;
using TakeoutSaaS.Domain.Stores.Repositories;
using TakeoutSaaS.Shared.Abstractions.Tenancy;
namespace TakeoutSaaS.Application.App.Stores.Handlers;
@@ -11,22 +10,28 @@ namespace TakeoutSaaS.Application.App.Stores.Handlers;
/// </summary>
public sealed class DeleteStoreEmployeeShiftCommandHandler(
IStoreRepository storeRepository,
ITenantProvider tenantProvider,
ILogger<DeleteStoreEmployeeShiftCommandHandler> logger)
: IRequestHandler<DeleteStoreEmployeeShiftCommand, bool>
{
/// <inheritdoc />
public async Task<bool> Handle(DeleteStoreEmployeeShiftCommand request, CancellationToken cancellationToken)
{
// 1. 读取排班
var tenantId = tenantProvider.GetCurrentTenantId();
// 1. 读取门店并解析租户
var store = await storeRepository.FindByIdAsync(request.StoreId, tenantId: null, cancellationToken);
if (store is null)
{
return false;
}
var tenantId = store.TenantId;
// 2. (空行后) 读取排班
var shift = await storeRepository.FindShiftByIdAsync(request.ShiftId, tenantId, cancellationToken);
if (shift is null || shift.StoreId != request.StoreId)
{
return false;
}
// 2. 删除
// 3. (空行后) 删除
await storeRepository.DeleteShiftAsync(request.ShiftId, tenantId, cancellationToken);
await storeRepository.SaveChangesAsync(cancellationToken);
logger.LogInformation("删除排班 {ShiftId} 门店 {StoreId}", request.ShiftId, request.StoreId);

View File

@@ -2,7 +2,6 @@ using MediatR;
using Microsoft.Extensions.Logging;
using TakeoutSaaS.Application.App.Stores.Commands;
using TakeoutSaaS.Domain.Stores.Repositories;
using TakeoutSaaS.Shared.Abstractions.Tenancy;
namespace TakeoutSaaS.Application.App.Stores.Handlers;
@@ -11,15 +10,28 @@ namespace TakeoutSaaS.Application.App.Stores.Handlers;
/// </summary>
public sealed class DeleteStorePickupSlotCommandHandler(
IStoreRepository storeRepository,
ITenantProvider tenantProvider,
ILogger<DeleteStorePickupSlotCommandHandler> logger)
: IRequestHandler<DeleteStorePickupSlotCommand, bool>
{
/// <inheritdoc />
public async Task<bool> Handle(DeleteStorePickupSlotCommand request, CancellationToken cancellationToken)
{
// 1. 删除档期
var tenantId = tenantProvider.GetCurrentTenantId();
// 1. 读取门店并解析租户
var store = await storeRepository.FindByIdAsync(request.StoreId, tenantId: null, cancellationToken);
if (store is null)
{
return false;
}
var tenantId = store.TenantId;
// 2. (空行后) 校验档期归属
var slot = await storeRepository.FindPickupSlotByIdAsync(request.SlotId, tenantId, cancellationToken);
if (slot is null || slot.StoreId != request.StoreId)
{
return false;
}
// 3. (空行后) 删除档期
await storeRepository.DeletePickupSlotAsync(request.SlotId, tenantId, cancellationToken);
await storeRepository.SaveChangesAsync(cancellationToken);
logger.LogInformation("删除自提档期 {SlotId}", request.SlotId);

View File

@@ -5,7 +5,6 @@ using TakeoutSaaS.Domain.Stores.Enums;
using TakeoutSaaS.Domain.Stores.Repositories;
using TakeoutSaaS.Shared.Abstractions.Constants;
using TakeoutSaaS.Shared.Abstractions.Exceptions;
using TakeoutSaaS.Shared.Abstractions.Tenancy;
namespace TakeoutSaaS.Application.App.Stores.Handlers;
@@ -14,7 +13,6 @@ namespace TakeoutSaaS.Application.App.Stores.Handlers;
/// </summary>
public sealed class DeleteStoreQualificationCommandHandler(
IStoreRepository storeRepository,
ITenantProvider tenantProvider,
ILogger<DeleteStoreQualificationCommandHandler> logger)
: IRequestHandler<DeleteStoreQualificationCommand, bool>
{
@@ -22,12 +20,12 @@ public sealed class DeleteStoreQualificationCommandHandler(
public async Task<bool> Handle(DeleteStoreQualificationCommand request, CancellationToken cancellationToken)
{
// 1. 校验门店存在
var tenantId = tenantProvider.GetCurrentTenantId();
var store = await storeRepository.FindByIdAsync(request.StoreId, tenantId, cancellationToken);
var store = await storeRepository.FindByIdAsync(request.StoreId, tenantId: null, cancellationToken);
if (store is null)
{
throw new BusinessException(ErrorCodes.NotFound, "门店不存在");
}
var tenantId = store.TenantId;
// 2. (空行后) 审核中门店禁止删除资质
if (store.AuditStatus == StoreAuditStatus.Pending)

View File

@@ -2,7 +2,7 @@ using MediatR;
using Microsoft.Extensions.Logging;
using TakeoutSaaS.Application.App.Stores.Commands;
using TakeoutSaaS.Domain.Merchants.Repositories;
using TakeoutSaaS.Shared.Abstractions.Tenancy;
using TakeoutSaaS.Domain.Stores.Repositories;
namespace TakeoutSaaS.Application.App.Stores.Handlers;
@@ -11,21 +11,29 @@ namespace TakeoutSaaS.Application.App.Stores.Handlers;
/// </summary>
public sealed class DeleteStoreStaffCommandHandler(
IMerchantRepository merchantRepository,
ITenantProvider tenantProvider,
IStoreRepository storeRepository,
ILogger<DeleteStoreStaffCommandHandler> logger)
: IRequestHandler<DeleteStoreStaffCommand, bool>
{
/// <inheritdoc />
public async Task<bool> Handle(DeleteStoreStaffCommand request, CancellationToken cancellationToken)
{
var tenantId = tenantProvider.GetCurrentTenantId();
// 1. 读取门店并解析租户
var store = await storeRepository.FindByIdAsync(request.StoreId, tenantId: null, cancellationToken);
if (store is null)
{
return false;
}
var tenantId = store.TenantId;
// 2. (空行后) 校验员工归属
var staff = await merchantRepository.FindStaffByIdAsync(request.StaffId, tenantId, cancellationToken);
if (staff is null || staff.StoreId != request.StoreId)
{
return false;
}
// 逻辑删除未定义,直接物理删除
// 3. (空行后) 逻辑删除未定义,直接物理删除
await merchantRepository.DeleteStaffAsync(staff.Id, tenantId, cancellationToken);
await merchantRepository.SaveChangesAsync(cancellationToken);
logger.LogInformation("删除门店员工 {StaffId} 门店 {StoreId}", request.StaffId, request.StoreId);

View File

@@ -4,7 +4,6 @@ using TakeoutSaaS.Application.App.Stores.Commands;
using TakeoutSaaS.Domain.Stores.Repositories;
using TakeoutSaaS.Shared.Abstractions.Constants;
using TakeoutSaaS.Shared.Abstractions.Exceptions;
using TakeoutSaaS.Shared.Abstractions.Tenancy;
namespace TakeoutSaaS.Application.App.Stores.Handlers;
@@ -13,28 +12,34 @@ namespace TakeoutSaaS.Application.App.Stores.Handlers;
/// </summary>
public sealed class DeleteStoreTableAreaCommandHandler(
IStoreRepository storeRepository,
ITenantProvider tenantProvider,
ILogger<DeleteStoreTableAreaCommandHandler> logger)
: IRequestHandler<DeleteStoreTableAreaCommand, bool>
{
/// <inheritdoc />
public async Task<bool> Handle(DeleteStoreTableAreaCommand request, CancellationToken cancellationToken)
{
// 1. 读取区域
var tenantId = tenantProvider.GetCurrentTenantId();
// 1. 读取门店并解析租户
var store = await storeRepository.FindByIdAsync(request.StoreId, tenantId: null, cancellationToken);
if (store is null)
{
return false;
}
var tenantId = store.TenantId;
// 2. (空行后) 读取区域
var area = await storeRepository.FindTableAreaByIdAsync(request.AreaId, tenantId, cancellationToken);
if (area is null)
{
return false;
}
// 2. 校验门店归属
// 3. (空行后) 校验门店归属
if (area.StoreId != request.StoreId)
{
return false;
}
// 3. 校验区域下无桌码
// 4. (空行后) 校验区域下无桌码
var tables = await storeRepository.GetTablesAsync(request.StoreId, tenantId, cancellationToken);
var hasTable = tables.Any(x => x.AreaId == request.AreaId);
if (hasTable)
@@ -42,7 +47,7 @@ public sealed class DeleteStoreTableAreaCommandHandler(
throw new BusinessException(ErrorCodes.Conflict, "区域下仍有桌码,无法删除");
}
// 4. 删除
// 5. (空行后) 删除
await storeRepository.DeleteTableAreaAsync(request.AreaId, tenantId, cancellationToken);
await storeRepository.SaveChangesAsync(cancellationToken);
logger.LogInformation("删除桌台区域 {AreaId} 对应门店 {StoreId}", request.AreaId, request.StoreId);

View File

@@ -2,7 +2,6 @@ using MediatR;
using Microsoft.Extensions.Logging;
using TakeoutSaaS.Application.App.Stores.Commands;
using TakeoutSaaS.Domain.Stores.Repositories;
using TakeoutSaaS.Shared.Abstractions.Tenancy;
namespace TakeoutSaaS.Application.App.Stores.Handlers;
@@ -11,22 +10,28 @@ namespace TakeoutSaaS.Application.App.Stores.Handlers;
/// </summary>
public sealed class DeleteStoreTableCommandHandler(
IStoreRepository storeRepository,
ITenantProvider tenantProvider,
ILogger<DeleteStoreTableCommandHandler> logger)
: IRequestHandler<DeleteStoreTableCommand, bool>
{
/// <inheritdoc />
public async Task<bool> Handle(DeleteStoreTableCommand request, CancellationToken cancellationToken)
{
// 1. 读取桌码
var tenantId = tenantProvider.GetCurrentTenantId();
// 1. 读取门店并解析租户
var store = await storeRepository.FindByIdAsync(request.StoreId, tenantId: null, cancellationToken);
if (store is null)
{
return false;
}
var tenantId = store.TenantId;
// 2. (空行后) 读取桌码
var table = await storeRepository.FindTableByIdAsync(request.TableId, tenantId, cancellationToken);
if (table is null || table.StoreId != request.StoreId)
{
return false;
}
// 2. 删除
// 3. (空行后) 删除
await storeRepository.DeleteTableAsync(request.TableId, tenantId, cancellationToken);
await storeRepository.SaveChangesAsync(cancellationToken);
logger.LogInformation("删除桌码 {TableId} 对应门店 {StoreId}", request.TableId, request.StoreId);

View File

@@ -7,7 +7,6 @@ using QRCoder;
using TakeoutSaaS.Application.App.Stores.Dto;
using TakeoutSaaS.Application.App.Stores.Queries;
using TakeoutSaaS.Domain.Stores.Repositories;
using TakeoutSaaS.Shared.Abstractions.Tenancy;
namespace TakeoutSaaS.Application.App.Stores.Handlers;
@@ -16,7 +15,6 @@ namespace TakeoutSaaS.Application.App.Stores.Handlers;
/// </summary>
public sealed class ExportStoreTableQRCodesQueryHandler(
IStoreRepository storeRepository,
ITenantProvider tenantProvider,
ILogger<ExportStoreTableQRCodesQueryHandler> logger)
: IRequestHandler<ExportStoreTableQRCodesQuery, StoreTableExportResult?>
{
@@ -24,14 +22,14 @@ public sealed class ExportStoreTableQRCodesQueryHandler(
public async Task<StoreTableExportResult?> Handle(ExportStoreTableQRCodesQuery request, CancellationToken cancellationToken)
{
// 1. 校验门店存在
var tenantId = tenantProvider.GetCurrentTenantId();
var store = await storeRepository.FindByIdAsync(request.StoreId, tenantId, cancellationToken);
var store = await storeRepository.FindByIdAsync(request.StoreId, tenantId: null, cancellationToken);
if (store is null)
{
return null;
}
var tenantId = store.TenantId;
// 2. 获取桌码列表
// 2. (空行后) 获取桌码列表
var tables = await storeRepository.GetTablesAsync(request.StoreId, tenantId, cancellationToken);
if (request.AreaId.HasValue)
{

View File

@@ -7,7 +7,6 @@ using TakeoutSaaS.Domain.Stores.Entities;
using TakeoutSaaS.Domain.Stores.Repositories;
using TakeoutSaaS.Shared.Abstractions.Constants;
using TakeoutSaaS.Shared.Abstractions.Exceptions;
using TakeoutSaaS.Shared.Abstractions.Tenancy;
namespace TakeoutSaaS.Application.App.Stores.Handlers;
@@ -16,7 +15,6 @@ namespace TakeoutSaaS.Application.App.Stores.Handlers;
/// </summary>
public sealed class GenerateStoreTablesCommandHandler(
IStoreRepository storeRepository,
ITenantProvider tenantProvider,
ILogger<GenerateStoreTablesCommandHandler> logger)
: IRequestHandler<GenerateStoreTablesCommand, IReadOnlyList<StoreTableDto>>
{
@@ -24,14 +22,14 @@ public sealed class GenerateStoreTablesCommandHandler(
public async Task<IReadOnlyList<StoreTableDto>> Handle(GenerateStoreTablesCommand request, CancellationToken cancellationToken)
{
// 1. 校验门店存在
var tenantId = tenantProvider.GetCurrentTenantId();
var store = await storeRepository.FindByIdAsync(request.StoreId, tenantId, cancellationToken);
var store = await storeRepository.FindByIdAsync(request.StoreId, tenantId: null, cancellationToken);
if (store is null)
{
throw new BusinessException(ErrorCodes.NotFound, "门店不存在");
}
var tenantId = store.TenantId;
// 2. 校验区域归属
// 2. (空行后) 校验区域归属
if (request.AreaId.HasValue)
{
var area = await storeRepository.FindTableAreaByIdAsync(request.AreaId.Value, tenantId, cancellationToken);
@@ -41,7 +39,7 @@ public sealed class GenerateStoreTablesCommandHandler(
}
}
// 3. 校验桌码唯一性
// 3. (空行后) 校验桌码唯一性
var existingTables = await storeRepository.GetTablesAsync(request.StoreId, tenantId, cancellationToken);
var newCodes = Enumerable.Range(request.StartNumber, request.Count)
.Select(i => $"{request.TableCodePrefix.Trim()}{i}")
@@ -52,9 +50,10 @@ public sealed class GenerateStoreTablesCommandHandler(
throw new BusinessException(ErrorCodes.Conflict, "桌码已存在,生成失败");
}
// 4. 构建实体
// 4. (空行后) 构建实体并写入租户
var tables = newCodes.Select(code => new StoreTable
{
TenantId = tenantId,
StoreId = request.StoreId,
AreaId = request.AreaId,
TableCode = code,
@@ -62,12 +61,12 @@ public sealed class GenerateStoreTablesCommandHandler(
Tags = request.Tags?.Trim()
}).ToList();
// 5. 持久化
// 5. (空行后) 持久化
await storeRepository.AddTablesAsync(tables, cancellationToken);
await storeRepository.SaveChangesAsync(cancellationToken);
logger.LogInformation("批量创建桌码 {Count} 条 对应门店 {StoreId}", tables.Count, request.StoreId);
// 6. 返回 DTO
// 6. (空行后) 返回 DTO
return tables.Select(StoreMapping.ToDto).ToList();
}
}

View File

@@ -2,7 +2,6 @@ using MediatR;
using TakeoutSaaS.Application.App.Stores.Dto;
using TakeoutSaaS.Application.App.Stores.Queries;
using TakeoutSaaS.Domain.Stores.Repositories;
using TakeoutSaaS.Shared.Abstractions.Tenancy;
namespace TakeoutSaaS.Application.App.Stores.Handlers;
@@ -10,22 +9,27 @@ namespace TakeoutSaaS.Application.App.Stores.Handlers;
/// 可用自提档期查询处理器。
/// </summary>
public sealed class GetAvailablePickupSlotsQueryHandler(
IStoreRepository storeRepository,
ITenantProvider tenantProvider)
IStoreRepository storeRepository)
: IRequestHandler<GetAvailablePickupSlotsQuery, IReadOnlyList<StorePickupSlotDto>>
{
/// <inheritdoc />
public async Task<IReadOnlyList<StorePickupSlotDto>> Handle(GetAvailablePickupSlotsQuery request, CancellationToken cancellationToken)
{
var tenantId = tenantProvider.GetCurrentTenantId();
// 1. 读取门店并解析租户
var store = await storeRepository.FindByIdAsync(request.StoreId, tenantId: null, cancellationToken);
if (store is null)
{
return [];
}
var tenantId = store.TenantId;
var date = request.Date.Date;
// 1. 读取配置
// 2. (空行后) 读取配置
var setting = await storeRepository.GetPickupSettingAsync(request.StoreId, tenantId, cancellationToken);
var allowDays = setting?.AllowDaysAhead ?? 0;
var allowToday = setting?.AllowToday ?? false;
var defaultCutoff = setting?.DefaultCutoffMinutes ?? 30;
// 2. 校验日期范围
// 3. (空行后) 校验日期范围
if (!allowToday && date == DateTime.UtcNow.Date)
{
return [];
@@ -36,13 +40,13 @@ public sealed class GetAvailablePickupSlotsQueryHandler(
return [];
}
// 3. 读取档期
// 4. (空行后) 读取档期
var slots = await storeRepository.GetPickupSlotsAsync(request.StoreId, tenantId, cancellationToken);
var weekday = (int)date.DayOfWeek;
weekday = weekday == 0 ? 7 : weekday;
var nowUtc = DateTime.UtcNow;
// 4. 过滤可用
// 5. (空行后) 过滤可用
var available = slots
.Where(x => x.IsEnabled && ContainsDay(x.Weekdays, weekday))
.Select(slot =>

View File

@@ -2,7 +2,6 @@ using MediatR;
using TakeoutSaaS.Application.App.Stores.Dto;
using TakeoutSaaS.Application.App.Stores.Queries;
using TakeoutSaaS.Domain.Stores.Repositories;
using TakeoutSaaS.Shared.Abstractions.Tenancy;
namespace TakeoutSaaS.Application.App.Stores.Handlers;
@@ -10,20 +9,28 @@ namespace TakeoutSaaS.Application.App.Stores.Handlers;
/// 获取自提配置处理器。
/// </summary>
public sealed class GetStorePickupSettingQueryHandler(
IStoreRepository storeRepository,
ITenantProvider tenantProvider)
IStoreRepository storeRepository)
: IRequestHandler<GetStorePickupSettingQuery, StorePickupSettingDto?>
{
/// <inheritdoc />
public async Task<StorePickupSettingDto?> Handle(GetStorePickupSettingQuery request, CancellationToken cancellationToken)
{
var tenantId = tenantProvider.GetCurrentTenantId();
// 1. 读取门店并解析租户
var store = await storeRepository.FindByIdAsync(request.StoreId, tenantId: null, cancellationToken);
if (store is null)
{
return null;
}
var tenantId = store.TenantId;
// 2. (空行后) 读取配置
var setting = await storeRepository.GetPickupSettingAsync(request.StoreId, tenantId, cancellationToken);
if (setting is null)
{
return null;
}
// 3. (空行后) 返回 DTO
return new StorePickupSettingDto
{
Id = setting.Id,

View File

@@ -5,7 +5,6 @@ using TakeoutSaaS.Application.App.Stores.Queries;
using TakeoutSaaS.Domain.Stores.Repositories;
using TakeoutSaaS.Shared.Abstractions.Constants;
using TakeoutSaaS.Shared.Abstractions.Exceptions;
using TakeoutSaaS.Shared.Abstractions.Tenancy;
namespace TakeoutSaaS.Application.App.Stores.Handlers;
@@ -14,7 +13,6 @@ namespace TakeoutSaaS.Application.App.Stores.Handlers;
/// </summary>
public sealed class GetStoreTableContextQueryHandler(
IStoreRepository storeRepository,
ITenantProvider tenantProvider,
ILogger<GetStoreTableContextQueryHandler> logger)
: IRequestHandler<GetStoreTableContextQuery, StoreTableContextDto?>
{
@@ -22,7 +20,7 @@ public sealed class GetStoreTableContextQueryHandler(
public async Task<StoreTableContextDto?> Handle(GetStoreTableContextQuery request, CancellationToken cancellationToken)
{
// 1. 查询桌码
var tenantId = tenantProvider.GetCurrentTenantId();
var tenantId = request.TenantId;
var table = await storeRepository.FindTableByCodeAsync(request.TableCode, tenantId, cancellationToken);
if (table is null)
{

View File

@@ -3,7 +3,6 @@ using MediatR;
using TakeoutSaaS.Application.App.Stores.Dto;
using TakeoutSaaS.Application.App.Stores.Queries;
using TakeoutSaaS.Domain.Stores.Repositories;
using TakeoutSaaS.Shared.Abstractions.Tenancy;
namespace TakeoutSaaS.Application.App.Stores.Handlers;
@@ -11,8 +10,7 @@ namespace TakeoutSaaS.Application.App.Stores.Handlers;
/// 排班列表查询处理器。
/// </summary>
public sealed class ListStoreEmployeeShiftsQueryHandler(
IStoreRepository storeRepository,
ITenantProvider tenantProvider)
IStoreRepository storeRepository)
: IRequestHandler<ListStoreEmployeeShiftsQuery, IReadOnlyList<StoreEmployeeShiftDto>>
{
/// <inheritdoc />
@@ -21,9 +19,14 @@ public sealed class ListStoreEmployeeShiftsQueryHandler(
// 1. 时间范围
var from = request.From ?? DateTime.UtcNow.Date;
var to = request.To ?? from.AddDays(7);
var tenantId = tenantProvider.GetCurrentTenantId();
var store = await storeRepository.FindByIdAsync(request.StoreId, tenantId: null, cancellationToken);
if (store is null)
{
return [];
}
var tenantId = store.TenantId;
// 2. 查询排班
// 2. (空行后) 查询排班
var shifts = await storeRepository.GetShiftsAsync(request.StoreId, tenantId, from, to, cancellationToken);
if (request.StaffId.HasValue)
@@ -31,7 +34,7 @@ public sealed class ListStoreEmployeeShiftsQueryHandler(
shifts = shifts.Where(x => x.StaffId == request.StaffId.Value).ToList();
}
// 3. 映射 DTO
// 3. (空行后) 映射 DTO
return shifts.Select(StoreMapping.ToDto).ToList();
}
}

View File

@@ -2,7 +2,6 @@ using MediatR;
using TakeoutSaaS.Application.App.Stores.Dto;
using TakeoutSaaS.Application.App.Stores.Queries;
using TakeoutSaaS.Domain.Stores.Repositories;
using TakeoutSaaS.Shared.Abstractions.Tenancy;
namespace TakeoutSaaS.Application.App.Stores.Handlers;
@@ -10,14 +9,21 @@ namespace TakeoutSaaS.Application.App.Stores.Handlers;
/// 自提档期列表查询处理器。
/// </summary>
public sealed class ListStorePickupSlotsQueryHandler(
IStoreRepository storeRepository,
ITenantProvider tenantProvider)
IStoreRepository storeRepository)
: IRequestHandler<ListStorePickupSlotsQuery, IReadOnlyList<StorePickupSlotDto>>
{
/// <inheritdoc />
public async Task<IReadOnlyList<StorePickupSlotDto>> Handle(ListStorePickupSlotsQuery request, CancellationToken cancellationToken)
{
var tenantId = tenantProvider.GetCurrentTenantId();
// 1. 读取门店并解析租户
var store = await storeRepository.FindByIdAsync(request.StoreId, tenantId: null, cancellationToken);
if (store is null)
{
return [];
}
var tenantId = store.TenantId;
// 2. (空行后) 查询档期列表
var slots = await storeRepository.GetPickupSlotsAsync(request.StoreId, tenantId, cancellationToken);
return slots
.Select(x => new StorePickupSlotDto

View File

@@ -4,7 +4,6 @@ using TakeoutSaaS.Application.App.Stores.Dto;
using TakeoutSaaS.Application.App.Stores.Queries;
using TakeoutSaaS.Domain.Merchants.Repositories;
using TakeoutSaaS.Domain.Stores.Repositories;
using TakeoutSaaS.Shared.Abstractions.Tenancy;
namespace TakeoutSaaS.Application.App.Stores.Handlers;
@@ -13,22 +12,21 @@ namespace TakeoutSaaS.Application.App.Stores.Handlers;
/// </summary>
public sealed class ListStoreStaffQueryHandler(
IMerchantRepository merchantRepository,
IStoreRepository storeRepository,
ITenantProvider tenantProvider)
IStoreRepository storeRepository)
: IRequestHandler<ListStoreStaffQuery, IReadOnlyList<StoreStaffDto>>
{
/// <inheritdoc />
public async Task<IReadOnlyList<StoreStaffDto>> Handle(ListStoreStaffQuery request, CancellationToken cancellationToken)
{
// 1. 校验门店存在
var tenantId = tenantProvider.GetCurrentTenantId();
var store = await storeRepository.FindByIdAsync(request.StoreId, tenantId, cancellationToken);
var store = await storeRepository.FindByIdAsync(request.StoreId, tenantId: null, cancellationToken);
if (store is null)
{
return [];
}
var tenantId = store.TenantId;
// 2. 查询员工
// 2. (空行后) 查询员工
var staffs = await merchantRepository.GetStaffByStoreAsync(request.StoreId, tenantId, cancellationToken);
if (request.RoleType.HasValue)
@@ -41,7 +39,7 @@ public sealed class ListStoreStaffQueryHandler(
staffs = staffs.Where(x => x.Status == request.Status.Value).ToList();
}
// 3. 映射 DTO
// 3. (空行后) 映射 DTO
return staffs.Select(StoreMapping.ToDto).ToList();
}
}

View File

@@ -3,24 +3,30 @@ using MediatR;
using TakeoutSaaS.Application.App.Stores.Dto;
using TakeoutSaaS.Application.App.Stores.Queries;
using TakeoutSaaS.Domain.Stores.Repositories;
using TakeoutSaaS.Shared.Abstractions.Tenancy;
namespace TakeoutSaaS.Application.App.Stores.Handlers;
/// <summary>
/// 桌台区域列表查询处理器。
/// </summary>
public sealed class ListStoreTableAreasQueryHandler(IStoreRepository storeRepository, ITenantProvider tenantProvider)
public sealed class ListStoreTableAreasQueryHandler(IStoreRepository storeRepository)
: IRequestHandler<ListStoreTableAreasQuery, IReadOnlyList<StoreTableAreaDto>>
{
/// <inheritdoc />
public async Task<IReadOnlyList<StoreTableAreaDto>> Handle(ListStoreTableAreasQuery request, CancellationToken cancellationToken)
{
// 1. 查询区域列表
var tenantId = tenantProvider.GetCurrentTenantId();
// 1. 查询门店并解析租户
var store = await storeRepository.FindByIdAsync(request.StoreId, tenantId: null, cancellationToken);
if (store is null)
{
return [];
}
var tenantId = store.TenantId;
// 2. (空行后) 查询区域列表
var areas = await storeRepository.GetTableAreasAsync(request.StoreId, tenantId, cancellationToken);
// 2. 映射 DTO
// 3. (空行后) 映射 DTO
return areas.Select(StoreMapping.ToDto).ToList();
}
}

View File

@@ -3,7 +3,6 @@ using MediatR;
using TakeoutSaaS.Application.App.Stores.Dto;
using TakeoutSaaS.Application.App.Stores.Queries;
using TakeoutSaaS.Domain.Stores.Repositories;
using TakeoutSaaS.Shared.Abstractions.Tenancy;
namespace TakeoutSaaS.Application.App.Stores.Handlers;
@@ -11,18 +10,24 @@ namespace TakeoutSaaS.Application.App.Stores.Handlers;
/// 桌码列表查询处理器。
/// </summary>
public sealed class ListStoreTablesQueryHandler(
IStoreRepository storeRepository,
ITenantProvider tenantProvider)
IStoreRepository storeRepository)
: IRequestHandler<ListStoreTablesQuery, IReadOnlyList<StoreTableDto>>
{
/// <inheritdoc />
public async Task<IReadOnlyList<StoreTableDto>> Handle(ListStoreTablesQuery request, CancellationToken cancellationToken)
{
// 1. 查询桌码列表
var tenantId = tenantProvider.GetCurrentTenantId();
// 1. 查询门店并解析租户
var store = await storeRepository.FindByIdAsync(request.StoreId, tenantId: null, cancellationToken);
if (store is null)
{
return [];
}
var tenantId = store.TenantId;
// 2. (空行后) 查询桌码列表
var tables = await storeRepository.GetTablesAsync(request.StoreId, tenantId, cancellationToken);
// 2. 过滤
// 3. (空行后) 过滤
if (request.AreaId.HasValue)
{
tables = tables.Where(x => x.AreaId == request.AreaId.Value).ToList();
@@ -33,7 +38,7 @@ public sealed class ListStoreTablesQueryHandler(
tables = tables.Where(x => x.Status == request.Status.Value).ToList();
}
// 3. 映射 DTO
// 4. (空行后) 映射 DTO
return tables.Select(StoreMapping.ToDto).ToList();
}
}

View File

@@ -8,7 +8,6 @@ using TakeoutSaaS.Domain.Stores.Repositories;
using TakeoutSaaS.Shared.Abstractions.Constants;
using TakeoutSaaS.Shared.Abstractions.Exceptions;
using TakeoutSaaS.Shared.Abstractions.Security;
using TakeoutSaaS.Shared.Abstractions.Tenancy;
namespace TakeoutSaaS.Application.App.Stores.Handlers;
@@ -17,7 +16,6 @@ namespace TakeoutSaaS.Application.App.Stores.Handlers;
/// </summary>
public sealed class SubmitStoreAuditCommandHandler(
IStoreRepository storeRepository,
ITenantProvider tenantProvider,
ICurrentUserAccessor currentUserAccessor,
IMediator mediator,
ILogger<SubmitStoreAuditCommandHandler> logger)
@@ -27,12 +25,12 @@ public sealed class SubmitStoreAuditCommandHandler(
public async Task<bool> Handle(SubmitStoreAuditCommand request, CancellationToken cancellationToken)
{
// 1. 校验门店存在
var tenantId = tenantProvider.GetCurrentTenantId();
var store = await storeRepository.FindByIdAsync(request.StoreId, tenantId, cancellationToken);
var store = await storeRepository.FindByIdAsync(request.StoreId, tenantId: null, cancellationToken);
if (store is null)
{
throw new BusinessException(ErrorCodes.NotFound, "门店不存在");
}
var tenantId = store.TenantId;
if (store.AuditStatus is not StoreAuditStatus.Draft and not StoreAuditStatus.Rejected)
{
@@ -53,6 +51,7 @@ public sealed class SubmitStoreAuditCommandHandler(
await storeRepository.UpdateStoreAsync(store, cancellationToken);
await storeRepository.AddAuditRecordAsync(new StoreAuditRecord
{
TenantId = tenantId,
StoreId = store.Id,
Action = StoreAuditAction.AutoActivate,
PreviousStatus = previousStatus,
@@ -87,6 +86,7 @@ public sealed class SubmitStoreAuditCommandHandler(
await storeRepository.UpdateStoreAsync(store, cancellationToken);
await storeRepository.AddAuditRecordAsync(new StoreAuditRecord
{
TenantId = tenantId,
StoreId = store.Id,
Action = action,
PreviousStatus = previous,

View File

@@ -6,7 +6,6 @@ using TakeoutSaaS.Domain.Stores.Enums;
using TakeoutSaaS.Domain.Stores.Repositories;
using TakeoutSaaS.Shared.Abstractions.Constants;
using TakeoutSaaS.Shared.Abstractions.Exceptions;
using TakeoutSaaS.Shared.Abstractions.Tenancy;
namespace TakeoutSaaS.Application.App.Stores.Handlers;
@@ -15,7 +14,6 @@ namespace TakeoutSaaS.Application.App.Stores.Handlers;
/// </summary>
public sealed class ToggleBusinessStatusCommandHandler(
IStoreRepository storeRepository,
ITenantProvider tenantProvider,
ILogger<ToggleBusinessStatusCommandHandler> logger)
: IRequestHandler<ToggleBusinessStatusCommand, StoreDto>
{
@@ -23,8 +21,7 @@ public sealed class ToggleBusinessStatusCommandHandler(
public async Task<StoreDto> Handle(ToggleBusinessStatusCommand request, CancellationToken cancellationToken)
{
// 1. 校验门店存在
var tenantId = tenantProvider.GetCurrentTenantId();
var store = await storeRepository.FindByIdAsync(request.StoreId, tenantId, cancellationToken);
var store = await storeRepository.FindByIdAsync(request.StoreId, tenantId: null, cancellationToken);
if (store is null)
{
throw new BusinessException(ErrorCodes.NotFound, "门店不存在");

View File

@@ -6,7 +6,6 @@ using TakeoutSaaS.Domain.Stores.Entities;
using TakeoutSaaS.Domain.Stores.Repositories;
using TakeoutSaaS.Shared.Abstractions.Constants;
using TakeoutSaaS.Shared.Abstractions.Exceptions;
using TakeoutSaaS.Shared.Abstractions.Tenancy;
namespace TakeoutSaaS.Application.App.Stores.Handlers;
@@ -15,32 +14,37 @@ namespace TakeoutSaaS.Application.App.Stores.Handlers;
/// </summary>
public sealed class UpdateStoreBusinessHourCommandHandler(
IStoreRepository storeRepository,
ITenantProvider tenantProvider,
ILogger<UpdateStoreBusinessHourCommandHandler> logger)
: IRequestHandler<UpdateStoreBusinessHourCommand, StoreBusinessHourDto?>
{
private readonly IStoreRepository _storeRepository = storeRepository;
private readonly ITenantProvider _tenantProvider = tenantProvider;
private readonly ILogger<UpdateStoreBusinessHourCommandHandler> _logger = logger;
/// <inheritdoc />
public async Task<StoreBusinessHourDto?> Handle(UpdateStoreBusinessHourCommand request, CancellationToken cancellationToken)
{
// 1. 读取时段
var tenantId = _tenantProvider.GetCurrentTenantId();
// 1. 读取门店并解析租户
var store = await _storeRepository.FindByIdAsync(request.StoreId, tenantId: null, cancellationToken);
if (store is null)
{
return null;
}
var tenantId = store.TenantId;
// 2. (空行后) 读取时段
var existing = await _storeRepository.FindBusinessHourByIdAsync(request.BusinessHourId, tenantId, cancellationToken);
if (existing is null)
{
return null;
}
// 2. 校验门店归属
// 3. (空行后) 校验门店归属
if (existing.StoreId != request.StoreId)
{
throw new BusinessException(ErrorCodes.ValidationFailed, "营业时段不属于该门店");
}
// 3. 更新字段
// 4. (空行后) 更新字段
existing.DayOfWeek = request.DayOfWeek;
existing.HourType = request.HourType;
existing.StartTime = request.StartTime;
@@ -48,12 +52,12 @@ public sealed class UpdateStoreBusinessHourCommandHandler(
existing.CapacityLimit = request.CapacityLimit;
existing.Notes = request.Notes?.Trim();
// 4. 持久化
// 5. (空行后) 持久化
await _storeRepository.UpdateBusinessHourAsync(existing, cancellationToken);
await _storeRepository.SaveChangesAsync(cancellationToken);
_logger.LogInformation("更新营业时段 {BusinessHourId} 对应门店 {StoreId}", existing.Id, existing.StoreId);
// 5. 返回 DTO
// 6. (空行后) 返回 DTO
return StoreMapping.ToDto(existing);
}
}

View File

@@ -6,7 +6,6 @@ using TakeoutSaaS.Domain.Merchants.Repositories;
using TakeoutSaaS.Domain.Stores.Repositories;
using TakeoutSaaS.Shared.Abstractions.Constants;
using TakeoutSaaS.Shared.Abstractions.Exceptions;
using TakeoutSaaS.Shared.Abstractions.Tenancy;
namespace TakeoutSaaS.Application.App.Stores.Handlers;
@@ -16,35 +15,41 @@ namespace TakeoutSaaS.Application.App.Stores.Handlers;
public sealed class UpdateStoreEmployeeShiftCommandHandler(
IStoreRepository storeRepository,
IMerchantRepository merchantRepository,
ITenantProvider tenantProvider,
ILogger<UpdateStoreEmployeeShiftCommandHandler> logger)
: IRequestHandler<UpdateStoreEmployeeShiftCommand, StoreEmployeeShiftDto?>
{
/// <inheritdoc />
public async Task<StoreEmployeeShiftDto?> Handle(UpdateStoreEmployeeShiftCommand request, CancellationToken cancellationToken)
{
// 1. 读取排班
var tenantId = tenantProvider.GetCurrentTenantId();
// 1. 读取门店并解析租户
var store = await storeRepository.FindByIdAsync(request.StoreId, tenantId: null, cancellationToken);
if (store is null)
{
return null;
}
var tenantId = store.TenantId;
// 2. (空行后) 读取排班
var shift = await storeRepository.FindShiftByIdAsync(request.ShiftId, tenantId, cancellationToken);
if (shift is null)
{
return null;
}
// 2. 校验门店归属
// 3. (空行后) 校验门店归属
if (shift.StoreId != request.StoreId)
{
throw new BusinessException(ErrorCodes.ValidationFailed, "排班不属于该门店");
}
// 3. 校验员工归属
// 4. (空行后) 校验员工归属
var staff = await merchantRepository.FindStaffByIdAsync(request.StaffId, tenantId, cancellationToken);
if (staff is null || (staff.StoreId.HasValue && staff.StoreId != request.StoreId))
{
throw new BusinessException(ErrorCodes.ValidationFailed, "员工不存在或不属于该门店");
}
// 4. 冲突校验
// 5. (空行后) 冲突校验
var shifts = await storeRepository.GetShiftsAsync(request.StoreId, tenantId, request.ShiftDate.Date, request.ShiftDate.Date, cancellationToken);
var hasConflict = shifts.Any(x => x.Id != request.ShiftId && x.StaffId == request.StaffId && x.ShiftDate == request.ShiftDate);
if (hasConflict)
@@ -52,7 +57,7 @@ public sealed class UpdateStoreEmployeeShiftCommandHandler(
throw new BusinessException(ErrorCodes.Conflict, "该员工当日已存在排班");
}
// 5. 更新字段
// 6. (空行后) 更新字段
shift.StaffId = request.StaffId;
shift.ShiftDate = request.ShiftDate.Date;
shift.StartTime = request.StartTime;
@@ -60,12 +65,12 @@ public sealed class UpdateStoreEmployeeShiftCommandHandler(
shift.RoleType = request.RoleType;
shift.Notes = request.Notes?.Trim();
// 6. 持久化
// 7. (空行后) 持久化
await storeRepository.UpdateShiftAsync(shift, cancellationToken);
await storeRepository.SaveChangesAsync(cancellationToken);
logger.LogInformation("更新排班 {ShiftId} 员工 {StaffId} 门店 {StoreId}", shift.Id, shift.StaffId, shift.StoreId);
// 7. 返回 DTO
// 8. (空行后) 返回 DTO
return StoreMapping.ToDto(shift);
}
}

View File

@@ -5,7 +5,6 @@ using TakeoutSaaS.Application.App.Stores.Dto;
using TakeoutSaaS.Domain.Stores.Repositories;
using TakeoutSaaS.Shared.Abstractions.Constants;
using TakeoutSaaS.Shared.Abstractions.Exceptions;
using TakeoutSaaS.Shared.Abstractions.Tenancy;
namespace TakeoutSaaS.Application.App.Stores.Handlers;
@@ -14,22 +13,28 @@ namespace TakeoutSaaS.Application.App.Stores.Handlers;
/// </summary>
public sealed class UpdateStorePickupSlotCommandHandler(
IStoreRepository storeRepository,
ITenantProvider tenantProvider,
ILogger<UpdateStorePickupSlotCommandHandler> logger)
: IRequestHandler<UpdateStorePickupSlotCommand, StorePickupSlotDto?>
{
/// <inheritdoc />
public async Task<StorePickupSlotDto?> Handle(UpdateStorePickupSlotCommand request, CancellationToken cancellationToken)
{
// 1. 查询档期
var tenantId = tenantProvider.GetCurrentTenantId();
// 1. 读取门店并解析租户
var store = await storeRepository.FindByIdAsync(request.StoreId, tenantId: null, cancellationToken);
if (store is null)
{
return null;
}
var tenantId = store.TenantId;
// 2. (空行后) 查询档期
var slot = await storeRepository.FindPickupSlotByIdAsync(request.SlotId, tenantId, cancellationToken);
if (slot is null || slot.StoreId != request.StoreId)
{
return null;
}
// 2. 更新字段
// 3. (空行后) 更新字段
slot.Name = request.Name.Trim();
slot.StartTime = request.StartTime;
slot.EndTime = request.EndTime;

View File

@@ -6,7 +6,6 @@ using TakeoutSaaS.Domain.Stores.Enums;
using TakeoutSaaS.Domain.Stores.Repositories;
using TakeoutSaaS.Shared.Abstractions.Constants;
using TakeoutSaaS.Shared.Abstractions.Exceptions;
using TakeoutSaaS.Shared.Abstractions.Tenancy;
namespace TakeoutSaaS.Application.App.Stores.Handlers;
@@ -15,7 +14,6 @@ namespace TakeoutSaaS.Application.App.Stores.Handlers;
/// </summary>
public sealed class UpdateStoreQualificationCommandHandler(
IStoreRepository storeRepository,
ITenantProvider tenantProvider,
ILogger<UpdateStoreQualificationCommandHandler> logger)
: IRequestHandler<UpdateStoreQualificationCommand, StoreQualificationDto?>
{
@@ -23,12 +21,12 @@ public sealed class UpdateStoreQualificationCommandHandler(
public async Task<StoreQualificationDto?> Handle(UpdateStoreQualificationCommand request, CancellationToken cancellationToken)
{
// 1. 校验门店存在
var tenantId = tenantProvider.GetCurrentTenantId();
var store = await storeRepository.FindByIdAsync(request.StoreId, tenantId, cancellationToken);
var store = await storeRepository.FindByIdAsync(request.StoreId, tenantId: null, cancellationToken);
if (store is null)
{
throw new BusinessException(ErrorCodes.NotFound, "门店不存在");
}
var tenantId = store.TenantId;
// 2. (空行后) 审核中门店禁止修改资质
if (store.AuditStatus == StoreAuditStatus.Pending)

View File

@@ -6,7 +6,6 @@ using TakeoutSaaS.Domain.Merchants.Repositories;
using TakeoutSaaS.Domain.Stores.Repositories;
using TakeoutSaaS.Shared.Abstractions.Constants;
using TakeoutSaaS.Shared.Abstractions.Exceptions;
using TakeoutSaaS.Shared.Abstractions.Tenancy;
namespace TakeoutSaaS.Application.App.Stores.Handlers;
@@ -16,7 +15,6 @@ namespace TakeoutSaaS.Application.App.Stores.Handlers;
public sealed class UpdateStoreStaffCommandHandler(
IMerchantRepository merchantRepository,
IStoreRepository storeRepository,
ITenantProvider tenantProvider,
ILogger<UpdateStoreStaffCommandHandler> logger)
: IRequestHandler<UpdateStoreStaffCommand, StoreStaffDto?>
{
@@ -24,32 +22,32 @@ public sealed class UpdateStoreStaffCommandHandler(
public async Task<StoreStaffDto?> Handle(UpdateStoreStaffCommand request, CancellationToken cancellationToken)
{
// 1. 校验门店
var tenantId = tenantProvider.GetCurrentTenantId();
var store = await storeRepository.FindByIdAsync(request.StoreId, tenantId, cancellationToken);
var store = await storeRepository.FindByIdAsync(request.StoreId, tenantId: null, cancellationToken);
if (store is null)
{
return null;
}
var tenantId = store.TenantId;
// 2. 读取员工
// 2. (空行后) 读取员工
var staff = await merchantRepository.FindStaffByIdAsync(request.StaffId, tenantId, cancellationToken);
if (staff is null || staff.StoreId != request.StoreId)
{
return null;
}
// 3. 更新字段
// 3. (空行后) 更新字段
staff.Name = request.Name.Trim();
staff.Phone = request.Phone.Trim();
staff.Email = request.Email?.Trim();
staff.RoleType = request.RoleType;
staff.Status = request.Status;
// 4. 持久化
// 4. (空行后) 持久化
await merchantRepository.SaveChangesAsync(cancellationToken);
logger.LogInformation("更新门店员工 {StaffId} 门店 {StoreId}", staff.Id, staff.StoreId);
// 5. 返回 DTO
// 5. (空行后) 返回 DTO
return StoreMapping.ToDto(staff);
}
}

View File

@@ -5,7 +5,6 @@ using TakeoutSaaS.Application.App.Stores.Dto;
using TakeoutSaaS.Domain.Stores.Repositories;
using TakeoutSaaS.Shared.Abstractions.Constants;
using TakeoutSaaS.Shared.Abstractions.Exceptions;
using TakeoutSaaS.Shared.Abstractions.Tenancy;
namespace TakeoutSaaS.Application.App.Stores.Handlers;
@@ -14,28 +13,34 @@ namespace TakeoutSaaS.Application.App.Stores.Handlers;
/// </summary>
public sealed class UpdateStoreTableAreaCommandHandler(
IStoreRepository storeRepository,
ITenantProvider tenantProvider,
ILogger<UpdateStoreTableAreaCommandHandler> logger)
: IRequestHandler<UpdateStoreTableAreaCommand, StoreTableAreaDto?>
{
/// <inheritdoc />
public async Task<StoreTableAreaDto?> Handle(UpdateStoreTableAreaCommand request, CancellationToken cancellationToken)
{
// 1. 读取区域
var tenantId = tenantProvider.GetCurrentTenantId();
// 1. 读取门店并解析租户
var store = await storeRepository.FindByIdAsync(request.StoreId, tenantId: null, cancellationToken);
if (store is null)
{
return null;
}
var tenantId = store.TenantId;
// 2. (空行后) 读取区域
var area = await storeRepository.FindTableAreaByIdAsync(request.AreaId, tenantId, cancellationToken);
if (area is null)
{
return null;
}
// 2. 校验门店归属
// 3. (空行后) 校验门店归属
if (area.StoreId != request.StoreId)
{
throw new BusinessException(ErrorCodes.ValidationFailed, "区域不属于该门店");
}
// 3. 名称唯一校验
// 4. (空行后) 名称唯一校验
var areas = await storeRepository.GetTableAreasAsync(request.StoreId, tenantId, cancellationToken);
var hasDuplicate = areas.Any(x => x.Id != request.AreaId && x.Name.Equals(request.Name, StringComparison.OrdinalIgnoreCase));
if (hasDuplicate)
@@ -43,17 +48,17 @@ public sealed class UpdateStoreTableAreaCommandHandler(
throw new BusinessException(ErrorCodes.Conflict, "区域名称已存在");
}
// 4. 更新字段
// 5. (空行后) 更新字段
area.Name = request.Name.Trim();
area.Description = request.Description?.Trim();
area.SortOrder = request.SortOrder;
// 5. 持久化
// 6. (空行后) 持久化
await storeRepository.UpdateTableAreaAsync(area, cancellationToken);
await storeRepository.SaveChangesAsync(cancellationToken);
logger.LogInformation("更新桌台区域 {AreaId} 对应门店 {StoreId}", area.Id, area.StoreId);
// 6. 返回 DTO
// 7. (空行后) 返回 DTO
return StoreMapping.ToDto(area);
}
}

View File

@@ -6,7 +6,6 @@ using TakeoutSaaS.Application.App.Stores.Dto;
using TakeoutSaaS.Domain.Stores.Repositories;
using TakeoutSaaS.Shared.Abstractions.Constants;
using TakeoutSaaS.Shared.Abstractions.Exceptions;
using TakeoutSaaS.Shared.Abstractions.Tenancy;
namespace TakeoutSaaS.Application.App.Stores.Handlers;
@@ -15,28 +14,34 @@ namespace TakeoutSaaS.Application.App.Stores.Handlers;
/// </summary>
public sealed class UpdateStoreTableCommandHandler(
IStoreRepository storeRepository,
ITenantProvider tenantProvider,
ILogger<UpdateStoreTableCommandHandler> logger)
: IRequestHandler<UpdateStoreTableCommand, StoreTableDto?>
{
/// <inheritdoc />
public async Task<StoreTableDto?> Handle(UpdateStoreTableCommand request, CancellationToken cancellationToken)
{
// 1. 读取桌码
var tenantId = tenantProvider.GetCurrentTenantId();
// 1. 读取门店并解析租户
var store = await storeRepository.FindByIdAsync(request.StoreId, tenantId: null, cancellationToken);
if (store is null)
{
return null;
}
var tenantId = store.TenantId;
// 2. (空行后) 读取桌码
var table = await storeRepository.FindTableByIdAsync(request.TableId, tenantId, cancellationToken);
if (table is null)
{
return null;
}
// 2. 校验门店归属
// 3. (空行后) 校验门店归属
if (table.StoreId != request.StoreId)
{
throw new BusinessException(ErrorCodes.ValidationFailed, "桌码不属于该门店");
}
// 3. 校验区域归属
// 4. (空行后) 校验区域归属
if (request.AreaId.HasValue)
{
var area = await storeRepository.FindTableAreaByIdAsync(request.AreaId.Value, tenantId, cancellationToken);
@@ -46,7 +51,7 @@ public sealed class UpdateStoreTableCommandHandler(
}
}
// 4. 校验桌码唯一
// 5. (空行后) 校验桌码唯一
var tables = await storeRepository.GetTablesAsync(request.StoreId, tenantId, cancellationToken);
var exists = tables.Any(x => x.Id != request.TableId && x.TableCode.Equals(request.TableCode, StringComparison.OrdinalIgnoreCase));
if (exists)
@@ -54,19 +59,19 @@ public sealed class UpdateStoreTableCommandHandler(
throw new BusinessException(ErrorCodes.Conflict, "桌码已存在");
}
// 5. 更新字段
// 6. (空行后) 更新字段
table.AreaId = request.AreaId;
table.TableCode = request.TableCode.Trim();
table.Capacity = request.Capacity;
table.Tags = request.Tags?.Trim();
table.Status = request.Status;
// 6. 持久化
// 7. (空行后) 持久化
await storeRepository.UpdateTableAsync(table, cancellationToken);
await storeRepository.SaveChangesAsync(cancellationToken);
logger.LogInformation("更新桌码 {TableId} 对应门店 {StoreId}", table.Id, table.StoreId);
// 7. 返回 DTO
// 8. (空行后) 返回 DTO
return StoreMapping.ToDto(table);
}
}

View File

@@ -6,7 +6,6 @@ using TakeoutSaaS.Domain.Stores.Entities;
using TakeoutSaaS.Domain.Stores.Repositories;
using TakeoutSaaS.Shared.Abstractions.Constants;
using TakeoutSaaS.Shared.Abstractions.Exceptions;
using TakeoutSaaS.Shared.Abstractions.Tenancy;
namespace TakeoutSaaS.Application.App.Stores.Handlers;
@@ -15,7 +14,6 @@ namespace TakeoutSaaS.Application.App.Stores.Handlers;
/// </summary>
public sealed class UpsertStorePickupSettingCommandHandler(
IStoreRepository storeRepository,
ITenantProvider tenantProvider,
ILogger<UpsertStorePickupSettingCommandHandler> logger)
: IRequestHandler<UpsertStorePickupSettingCommand, StorePickupSettingDto>
{
@@ -23,14 +21,14 @@ public sealed class UpsertStorePickupSettingCommandHandler(
public async Task<StorePickupSettingDto> Handle(UpsertStorePickupSettingCommand request, CancellationToken cancellationToken)
{
// 1. 校验门店存在
var tenantId = tenantProvider.GetCurrentTenantId();
var store = await storeRepository.FindByIdAsync(request.StoreId, tenantId, cancellationToken);
var store = await storeRepository.FindByIdAsync(request.StoreId, tenantId: null, cancellationToken);
if (store is null)
{
throw new BusinessException(ErrorCodes.NotFound, "门店不存在");
}
var tenantId = store.TenantId;
// 2. 读取或创建配置
// 2. (空行后) 读取或创建配置
var setting = await storeRepository.GetPickupSettingAsync(request.StoreId, tenantId, cancellationToken);
if (setting is null)
{
@@ -42,7 +40,7 @@ public sealed class UpsertStorePickupSettingCommandHandler(
await storeRepository.AddPickupSettingAsync(setting, cancellationToken);
}
// 3. 更新字段
// 3. (空行后) 更新字段
setting.AllowToday = request.AllowToday;
setting.AllowDaysAhead = request.AllowDaysAhead;
setting.DefaultCutoffMinutes = request.DefaultCutoffMinutes;

View File

@@ -8,6 +8,11 @@ namespace TakeoutSaaS.Application.App.Stores.Queries;
/// </summary>
public sealed record GetStoreTableContextQuery : IRequest<StoreTableContextDto?>
{
/// <summary>
/// 租户 ID。
/// </summary>
public long TenantId { get; init; }
/// <summary>
/// 桌码。
/// </summary>

View File

@@ -13,6 +13,7 @@ public sealed class GetStoreTableContextQueryValidator : AbstractValidator<GetSt
/// </summary>
public GetStoreTableContextQueryValidator()
{
RuleFor(x => x.TenantId).GreaterThan(0);
RuleFor(x => x.TableCode).NotEmpty().MaximumLength(32);
}
}