docs: 标记自提档期完成
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Stores.Dto;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Stores.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// 创建自提档期命令。
|
||||
/// </summary>
|
||||
public sealed record CreateStorePickupSlotCommand : IRequest<StorePickupSlotDto>
|
||||
{
|
||||
/// <summary>
|
||||
/// 门店 ID。
|
||||
/// </summary>
|
||||
public long StoreId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 名称。
|
||||
/// </summary>
|
||||
public string Name { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 开始时间。
|
||||
/// </summary>
|
||||
public TimeSpan StartTime { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 结束时间。
|
||||
/// </summary>
|
||||
public TimeSpan EndTime { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 截单分钟。
|
||||
/// </summary>
|
||||
public int CutoffMinutes { get; init; } = 30;
|
||||
|
||||
/// <summary>
|
||||
/// 容量。
|
||||
/// </summary>
|
||||
public int Capacity { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 适用星期。
|
||||
/// </summary>
|
||||
public string Weekdays { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 是否启用。
|
||||
/// </summary>
|
||||
public bool IsEnabled { get; init; } = true;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using MediatR;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Stores.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// 删除自提档期命令。
|
||||
/// </summary>
|
||||
public sealed record DeleteStorePickupSlotCommand : IRequest<bool>
|
||||
{
|
||||
/// <summary>
|
||||
/// 档期 ID。
|
||||
/// </summary>
|
||||
public long SlotId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 门店 ID。
|
||||
/// </summary>
|
||||
public long StoreId { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Stores.Dto;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Stores.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// 更新自提档期命令。
|
||||
/// </summary>
|
||||
public sealed record UpdateStorePickupSlotCommand : IRequest<StorePickupSlotDto?>
|
||||
{
|
||||
/// <summary>
|
||||
/// 档期 ID。
|
||||
/// </summary>
|
||||
public long SlotId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 门店 ID。
|
||||
/// </summary>
|
||||
public long StoreId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 名称。
|
||||
/// </summary>
|
||||
public string Name { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 开始时间。
|
||||
/// </summary>
|
||||
public TimeSpan StartTime { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 结束时间。
|
||||
/// </summary>
|
||||
public TimeSpan EndTime { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 截单分钟。
|
||||
/// </summary>
|
||||
public int CutoffMinutes { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 容量。
|
||||
/// </summary>
|
||||
public int Capacity { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 适用星期。
|
||||
/// </summary>
|
||||
public string Weekdays { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 是否启用。
|
||||
/// </summary>
|
||||
public bool IsEnabled { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Stores.Dto;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Stores.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// 新增或更新自提配置命令。
|
||||
/// </summary>
|
||||
public sealed record UpsertStorePickupSettingCommand : IRequest<StorePickupSettingDto>
|
||||
{
|
||||
/// <summary>
|
||||
/// 门店 ID。
|
||||
/// </summary>
|
||||
public long StoreId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否允许当天。
|
||||
/// </summary>
|
||||
public bool AllowToday { get; init; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 可预约天数。
|
||||
/// </summary>
|
||||
public int AllowDaysAhead { get; init; } = 3;
|
||||
|
||||
/// <summary>
|
||||
/// 默认截单分钟。
|
||||
/// </summary>
|
||||
public int DefaultCutoffMinutes { get; init; } = 30;
|
||||
|
||||
/// <summary>
|
||||
/// 单笔最大份数。
|
||||
/// </summary>
|
||||
public int? MaxQuantityPerOrder { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using TakeoutSaaS.Shared.Abstractions.Serialization;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Stores.Dto;
|
||||
|
||||
/// <summary>
|
||||
/// 自提配置 DTO。
|
||||
/// </summary>
|
||||
public sealed record StorePickupSettingDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 配置 ID。
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(SnowflakeIdJsonConverter))]
|
||||
public long Id { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 门店 ID。
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(SnowflakeIdJsonConverter))]
|
||||
public long StoreId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否允许当天自提。
|
||||
/// </summary>
|
||||
public bool AllowToday { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 可预约天数。
|
||||
/// </summary>
|
||||
public int AllowDaysAhead { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 默认截单分钟。
|
||||
/// </summary>
|
||||
public int DefaultCutoffMinutes { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 单笔最大自提份数。
|
||||
/// </summary>
|
||||
public int? MaxQuantityPerOrder { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using TakeoutSaaS.Shared.Abstractions.Serialization;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Stores.Dto;
|
||||
|
||||
/// <summary>
|
||||
/// 自提档期 DTO。
|
||||
/// </summary>
|
||||
public sealed record StorePickupSlotDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 档期 ID。
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(SnowflakeIdJsonConverter))]
|
||||
public long Id { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 门店 ID。
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(SnowflakeIdJsonConverter))]
|
||||
public long StoreId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 名称。
|
||||
/// </summary>
|
||||
public string Name { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 开始时间。
|
||||
/// </summary>
|
||||
public TimeSpan StartTime { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 结束时间。
|
||||
/// </summary>
|
||||
public TimeSpan EndTime { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 截单分钟。
|
||||
/// </summary>
|
||||
public int CutoffMinutes { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 容量。
|
||||
/// </summary>
|
||||
public int Capacity { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 已占用。
|
||||
/// </summary>
|
||||
public int ReservedCount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 适用星期(1-7)。
|
||||
/// </summary>
|
||||
public string Weekdays { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 是否启用。
|
||||
/// </summary>
|
||||
public bool IsEnabled { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
using MediatR;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using TakeoutSaaS.Application.App.Stores.Commands;
|
||||
using TakeoutSaaS.Application.App.Stores.Dto;
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// 创建自提档期处理器。
|
||||
/// </summary>
|
||||
public sealed class CreateStorePickupSlotCommandHandler(
|
||||
IStoreRepository storeRepository,
|
||||
ITenantProvider tenantProvider,
|
||||
ILogger<CreateStorePickupSlotCommandHandler> logger)
|
||||
: IRequestHandler<CreateStorePickupSlotCommand, StorePickupSlotDto>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<StorePickupSlotDto> Handle(CreateStorePickupSlotCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 校验门店
|
||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||
var store = await storeRepository.FindByIdAsync(request.StoreId, tenantId, cancellationToken);
|
||||
if (store is null)
|
||||
{
|
||||
throw new BusinessException(ErrorCodes.NotFound, "门店不存在");
|
||||
}
|
||||
|
||||
// 2. 新建档期
|
||||
var slot = new StorePickupSlot
|
||||
{
|
||||
TenantId = tenantId,
|
||||
StoreId = request.StoreId,
|
||||
Name = request.Name.Trim(),
|
||||
StartTime = request.StartTime,
|
||||
EndTime = request.EndTime,
|
||||
CutoffMinutes = request.CutoffMinutes,
|
||||
Capacity = request.Capacity,
|
||||
ReservedCount = 0,
|
||||
Weekdays = request.Weekdays,
|
||||
IsEnabled = request.IsEnabled
|
||||
};
|
||||
await storeRepository.AddPickupSlotsAsync(new[] { slot }, cancellationToken);
|
||||
await storeRepository.SaveChangesAsync(cancellationToken);
|
||||
logger.LogInformation("创建自提档期 {SlotId} for store {StoreId}", slot.Id, request.StoreId);
|
||||
return new StorePickupSlotDto
|
||||
{
|
||||
Id = slot.Id,
|
||||
StoreId = slot.StoreId,
|
||||
Name = slot.Name,
|
||||
StartTime = slot.StartTime,
|
||||
EndTime = slot.EndTime,
|
||||
CutoffMinutes = slot.CutoffMinutes,
|
||||
Capacity = slot.Capacity,
|
||||
ReservedCount = slot.ReservedCount,
|
||||
Weekdays = slot.Weekdays,
|
||||
IsEnabled = slot.IsEnabled
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// 删除自提档期处理器。
|
||||
/// </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();
|
||||
await storeRepository.DeletePickupSlotAsync(request.SlotId, tenantId, cancellationToken);
|
||||
await storeRepository.SaveChangesAsync(cancellationToken);
|
||||
logger.LogInformation("删除自提档期 {SlotId}", request.SlotId);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
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 GetAvailablePickupSlotsQueryHandler(
|
||||
IStoreRepository storeRepository,
|
||||
ITenantProvider tenantProvider)
|
||||
: IRequestHandler<GetAvailablePickupSlotsQuery, IReadOnlyList<StorePickupSlotDto>>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<IReadOnlyList<StorePickupSlotDto>> Handle(GetAvailablePickupSlotsQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||
var date = request.Date.Date;
|
||||
// 1. 读取配置
|
||||
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. 校验日期范围
|
||||
if (!allowToday && date == DateTime.UtcNow.Date)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
if (date > DateTime.UtcNow.Date.AddDays(allowDays))
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
// 3. 读取档期
|
||||
var slots = await storeRepository.GetPickupSlotsAsync(request.StoreId, tenantId, cancellationToken);
|
||||
var weekday = (int)date.DayOfWeek;
|
||||
weekday = weekday == 0 ? 7 : weekday;
|
||||
var nowUtc = DateTime.UtcNow;
|
||||
|
||||
// 4. 过滤可用
|
||||
var available = slots
|
||||
.Where(x => x.IsEnabled && ContainsDay(x.Weekdays, weekday))
|
||||
.Select(slot =>
|
||||
{
|
||||
var cutoff = slot.CutoffMinutes == 0 ? defaultCutoff : slot.CutoffMinutes;
|
||||
var slotStartUtc = date.Add(slot.StartTime);
|
||||
// 判断截单
|
||||
var cutoffTime = slotStartUtc.AddMinutes(-cutoff);
|
||||
var isCutoff = nowUtc > cutoffTime;
|
||||
var remaining = slot.Capacity - slot.ReservedCount;
|
||||
return (slot, isCutoff, remaining);
|
||||
})
|
||||
.Where(x => !x.isCutoff && x.remaining > 0)
|
||||
.Select(x => new StorePickupSlotDto
|
||||
{
|
||||
Id = x.slot.Id,
|
||||
StoreId = x.slot.StoreId,
|
||||
Name = x.slot.Name,
|
||||
StartTime = x.slot.StartTime,
|
||||
EndTime = x.slot.EndTime,
|
||||
CutoffMinutes = x.slot.CutoffMinutes,
|
||||
Capacity = x.slot.Capacity,
|
||||
ReservedCount = x.slot.ReservedCount,
|
||||
Weekdays = x.slot.Weekdays,
|
||||
IsEnabled = x.slot.IsEnabled
|
||||
})
|
||||
.ToList();
|
||||
|
||||
return available;
|
||||
}
|
||||
|
||||
private static bool ContainsDay(string weekdays, int target)
|
||||
{
|
||||
// 解析适用星期
|
||||
var parts = weekdays.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
return parts.Any(p => int.TryParse(p, out var val) && val == target);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
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 GetStorePickupSettingQueryHandler(
|
||||
IStoreRepository storeRepository,
|
||||
ITenantProvider tenantProvider)
|
||||
: IRequestHandler<GetStorePickupSettingQuery, StorePickupSettingDto?>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<StorePickupSettingDto?> Handle(GetStorePickupSettingQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||
var setting = await storeRepository.GetPickupSettingAsync(request.StoreId, tenantId, cancellationToken);
|
||||
if (setting is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new StorePickupSettingDto
|
||||
{
|
||||
Id = setting.Id,
|
||||
StoreId = setting.StoreId,
|
||||
AllowToday = setting.AllowToday,
|
||||
AllowDaysAhead = setting.AllowDaysAhead,
|
||||
DefaultCutoffMinutes = setting.DefaultCutoffMinutes,
|
||||
MaxQuantityPerOrder = setting.MaxQuantityPerOrder
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
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 ListStorePickupSlotsQueryHandler(
|
||||
IStoreRepository storeRepository,
|
||||
ITenantProvider tenantProvider)
|
||||
: IRequestHandler<ListStorePickupSlotsQuery, IReadOnlyList<StorePickupSlotDto>>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<IReadOnlyList<StorePickupSlotDto>> Handle(ListStorePickupSlotsQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||
var slots = await storeRepository.GetPickupSlotsAsync(request.StoreId, tenantId, cancellationToken);
|
||||
return slots
|
||||
.Select(x => new StorePickupSlotDto
|
||||
{
|
||||
Id = x.Id,
|
||||
StoreId = x.StoreId,
|
||||
Name = x.Name,
|
||||
StartTime = x.StartTime,
|
||||
EndTime = x.EndTime,
|
||||
CutoffMinutes = x.CutoffMinutes,
|
||||
Capacity = x.Capacity,
|
||||
ReservedCount = x.ReservedCount,
|
||||
Weekdays = x.Weekdays,
|
||||
IsEnabled = x.IsEnabled
|
||||
})
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using MediatR;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using TakeoutSaaS.Application.App.Stores.Commands;
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// 更新自提档期处理器。
|
||||
/// </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();
|
||||
var slot = await storeRepository.FindPickupSlotByIdAsync(request.SlotId, tenantId, cancellationToken);
|
||||
if (slot is null || slot.StoreId != request.StoreId)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// 2. 更新字段
|
||||
slot.Name = request.Name.Trim();
|
||||
slot.StartTime = request.StartTime;
|
||||
slot.EndTime = request.EndTime;
|
||||
slot.CutoffMinutes = request.CutoffMinutes;
|
||||
slot.Capacity = request.Capacity;
|
||||
slot.Weekdays = request.Weekdays;
|
||||
slot.IsEnabled = request.IsEnabled;
|
||||
await storeRepository.UpdatePickupSlotAsync(slot, cancellationToken);
|
||||
await storeRepository.SaveChangesAsync(cancellationToken);
|
||||
logger.LogInformation("更新自提档期 {SlotId}", request.SlotId);
|
||||
return new StorePickupSlotDto
|
||||
{
|
||||
Id = slot.Id,
|
||||
StoreId = slot.StoreId,
|
||||
Name = slot.Name,
|
||||
StartTime = slot.StartTime,
|
||||
EndTime = slot.EndTime,
|
||||
CutoffMinutes = slot.CutoffMinutes,
|
||||
Capacity = slot.Capacity,
|
||||
ReservedCount = slot.ReservedCount,
|
||||
Weekdays = slot.Weekdays,
|
||||
IsEnabled = slot.IsEnabled
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
using MediatR;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using TakeoutSaaS.Application.App.Stores.Commands;
|
||||
using TakeoutSaaS.Application.App.Stores.Dto;
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// 自提配置维护处理器。
|
||||
/// </summary>
|
||||
public sealed class UpsertStorePickupSettingCommandHandler(
|
||||
IStoreRepository storeRepository,
|
||||
ITenantProvider tenantProvider,
|
||||
ILogger<UpsertStorePickupSettingCommandHandler> logger)
|
||||
: IRequestHandler<UpsertStorePickupSettingCommand, StorePickupSettingDto>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<StorePickupSettingDto> Handle(UpsertStorePickupSettingCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 校验门店存在
|
||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||
var store = await storeRepository.FindByIdAsync(request.StoreId, tenantId, cancellationToken);
|
||||
if (store is null)
|
||||
{
|
||||
throw new BusinessException(ErrorCodes.NotFound, "门店不存在");
|
||||
}
|
||||
|
||||
// 2. 读取或创建配置
|
||||
var setting = await storeRepository.GetPickupSettingAsync(request.StoreId, tenantId, cancellationToken);
|
||||
if (setting is null)
|
||||
{
|
||||
setting = new StorePickupSetting
|
||||
{
|
||||
TenantId = tenantId,
|
||||
StoreId = request.StoreId
|
||||
};
|
||||
await storeRepository.AddPickupSettingAsync(setting, cancellationToken);
|
||||
}
|
||||
|
||||
// 3. 更新字段
|
||||
setting.AllowToday = request.AllowToday;
|
||||
setting.AllowDaysAhead = request.AllowDaysAhead;
|
||||
setting.DefaultCutoffMinutes = request.DefaultCutoffMinutes;
|
||||
setting.MaxQuantityPerOrder = request.MaxQuantityPerOrder;
|
||||
await storeRepository.UpdatePickupSettingAsync(setting, cancellationToken);
|
||||
await storeRepository.SaveChangesAsync(cancellationToken);
|
||||
logger.LogInformation("更新门店 {StoreId} 自提配置", request.StoreId);
|
||||
return new StorePickupSettingDto
|
||||
{
|
||||
Id = setting.Id,
|
||||
StoreId = setting.StoreId,
|
||||
AllowToday = setting.AllowToday,
|
||||
AllowDaysAhead = setting.AllowDaysAhead,
|
||||
DefaultCutoffMinutes = setting.DefaultCutoffMinutes,
|
||||
MaxQuantityPerOrder = setting.MaxQuantityPerOrder
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Stores.Dto;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Stores.Queries;
|
||||
|
||||
/// <summary>
|
||||
/// 获取可用自提档期查询。
|
||||
/// </summary>
|
||||
public sealed record GetAvailablePickupSlotsQuery : IRequest<IReadOnlyList<StorePickupSlotDto>>
|
||||
{
|
||||
/// <summary>
|
||||
/// 门店 ID。
|
||||
/// </summary>
|
||||
public long StoreId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 目标日期(本地日期部分)。
|
||||
/// </summary>
|
||||
public DateTime Date { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Stores.Dto;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Stores.Queries;
|
||||
|
||||
/// <summary>
|
||||
/// 获取门店自提配置查询。
|
||||
/// </summary>
|
||||
public sealed record GetStorePickupSettingQuery : IRequest<StorePickupSettingDto?>
|
||||
{
|
||||
/// <summary>
|
||||
/// 门店 ID。
|
||||
/// </summary>
|
||||
public long StoreId { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Stores.Dto;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Stores.Queries;
|
||||
|
||||
/// <summary>
|
||||
/// 门店档期列表查询。
|
||||
/// </summary>
|
||||
public sealed record ListStorePickupSlotsQuery : IRequest<IReadOnlyList<StorePickupSlotDto>>
|
||||
{
|
||||
/// <summary>
|
||||
/// 门店 ID。
|
||||
/// </summary>
|
||||
public long StoreId { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using FluentValidation;
|
||||
using TakeoutSaaS.Application.App.Stores.Commands;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Stores.Validators;
|
||||
|
||||
/// <summary>
|
||||
/// 创建自提档期验证器。
|
||||
/// </summary>
|
||||
public sealed class CreateStorePickupSlotCommandValidator : AbstractValidator<CreateStorePickupSlotCommand>
|
||||
{
|
||||
/// <summary>
|
||||
/// 初始化规则。
|
||||
/// </summary>
|
||||
public CreateStorePickupSlotCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.StoreId).GreaterThan(0);
|
||||
RuleFor(x => x.Name).NotEmpty().MaximumLength(64);
|
||||
RuleFor(x => x.Capacity).GreaterThan(0);
|
||||
RuleFor(x => x.Weekdays).NotEmpty().MaximumLength(32);
|
||||
RuleFor(x => x.CutoffMinutes).GreaterThanOrEqualTo(0);
|
||||
RuleFor(x => x.EndTime).GreaterThan(x => x.StartTime);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using FluentValidation;
|
||||
using TakeoutSaaS.Application.App.Stores.Commands;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Stores.Validators;
|
||||
|
||||
/// <summary>
|
||||
/// 删除自提档期验证器。
|
||||
/// </summary>
|
||||
public sealed class DeleteStorePickupSlotCommandValidator : AbstractValidator<DeleteStorePickupSlotCommand>
|
||||
{
|
||||
/// <summary>
|
||||
/// 初始化规则。
|
||||
/// </summary>
|
||||
public DeleteStorePickupSlotCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.SlotId).GreaterThan(0);
|
||||
RuleFor(x => x.StoreId).GreaterThan(0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using FluentValidation;
|
||||
using TakeoutSaaS.Application.App.Stores.Commands;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Stores.Validators;
|
||||
|
||||
/// <summary>
|
||||
/// 更新自提档期验证器。
|
||||
/// </summary>
|
||||
public sealed class UpdateStorePickupSlotCommandValidator : AbstractValidator<UpdateStorePickupSlotCommand>
|
||||
{
|
||||
/// <summary>
|
||||
/// 初始化规则。
|
||||
/// </summary>
|
||||
public UpdateStorePickupSlotCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.SlotId).GreaterThan(0);
|
||||
RuleFor(x => x.StoreId).GreaterThan(0);
|
||||
RuleFor(x => x.Name).NotEmpty().MaximumLength(64);
|
||||
RuleFor(x => x.Capacity).GreaterThan(0);
|
||||
RuleFor(x => x.Weekdays).NotEmpty().MaximumLength(32);
|
||||
RuleFor(x => x.CutoffMinutes).GreaterThanOrEqualTo(0);
|
||||
RuleFor(x => x.EndTime).GreaterThan(x => x.StartTime);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using FluentValidation;
|
||||
using TakeoutSaaS.Application.App.Stores.Commands;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Stores.Validators;
|
||||
|
||||
/// <summary>
|
||||
/// 自提配置验证器。
|
||||
/// </summary>
|
||||
public sealed class UpsertStorePickupSettingCommandValidator : AbstractValidator<UpsertStorePickupSettingCommand>
|
||||
{
|
||||
/// <summary>
|
||||
/// 初始化规则。
|
||||
/// </summary>
|
||||
public UpsertStorePickupSettingCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.StoreId).GreaterThan(0);
|
||||
RuleFor(x => x.AllowDaysAhead).GreaterThanOrEqualTo(0);
|
||||
RuleFor(x => x.DefaultCutoffMinutes).GreaterThanOrEqualTo(0);
|
||||
RuleFor(x => x.MaxQuantityPerOrder).GreaterThan(0).When(x => x.MaxQuantityPerOrder.HasValue);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user