diff --git a/Document/12_BusinessTodo.md b/Document/12_BusinessTodo.md index 2e1525e..0d2cad0 100644 --- a/Document/12_BusinessTodo.md +++ b/Document/12_BusinessTodo.md @@ -14,8 +14,8 @@ - 已交付:新增套餐仓储与命令/查询/DTO(`src/Application/TakeoutSaaS.Application/App/Tenants`),Admin 端新增 `TenantPackagesController` 提供套餐列表/详情/创建/更新/删除接口。新增配额校验命令与租户接口 `/api/admin/v1/tenants/{id}/quotas/check`,基于当前订阅套餐限额校验并占用配额,超额抛出 409 并写入 `TenantQuotaUsage`。仓储注册于 `AddAppInfrastructure`。 - [x] 租户运营面板:欠费/到期告警、账单列表、公告通知接口,支持已读状态并在 Admin UI 展示。 - 已交付:新增账单/公告/通知实体与仓储,Admin 端提供 `/tenants/{id}/billings`(列表/详情/创建/标记支付)、`/announcements`(列表/详情/创建/更新/删除/已读)、`/notifications`(列表/已读)端点;权限码补充 `tenant-bill:*`、`tenant-announcement:*`、`tenant-notification:*`,种子模板更新;配额/订阅告警可通过通知表承载。 -- [ ] 门店管理:Store/StoreBusinessHour/StoreDeliveryZone/StoreHoliday CRUD 完整,含 GeoJSON 配送范围及能力开关。 - - 当前:仅有门店基础 CRUD(`src/Api/TakeoutSaaS.AdminApi/Controllers/StoresController.cs`),未实现营业时间、配送区/GeoJSON、节假日的命令/查询与 API;相关实体和仓储方法存在但未暴露。 +- [x] 门店管理:Store/StoreBusinessHour/StoreDeliveryZone/StoreHoliday CRUD 完整,含 GeoJSON 配送范围及能力开关。 + - 进展:已补充营业时间/配送区/节假日的命令、查询、验证与处理器,Admin API 新增子路由完成 CRUD,门店能力开关(预约/排队)已对外暴露;仓储扩展读写删除并保持租户过滤。 - [ ] 桌码管理:批量生成桌码、绑定区域/容量、导出二维码 ZIP(POST /api/admin/stores/{id}/tables 可下载)。 - 当前:模型/仓储已包含 `StoreTable`/`StoreTableArea`,但缺少命令/查询/控制器,未实现批量生成、区域容量绑定和二维码导出。 - [ ] 员工排班:创建员工、绑定门店角色、维护 StoreEmployeeShift,可查询未来 7 日排班。 diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/StoresController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/StoresController.cs index 6abdac2..3acccb7 100644 --- a/src/Api/TakeoutSaaS.AdminApi/Controllers/StoresController.cs +++ b/src/Api/TakeoutSaaS.AdminApi/Controllers/StoresController.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using MediatR; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; @@ -114,4 +115,196 @@ public sealed class StoresController(IMediator mediator) : BaseApiController ? ApiResponse.Ok(null) : ApiResponse.Error(ErrorCodes.NotFound, "门店不存在"); } + + /// + /// 查询门店营业时段。 + /// + [HttpGet("{storeId:long}/business-hours")] + [PermissionAuthorize("store:read")] + [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] + public async Task>> ListBusinessHours(long storeId, CancellationToken cancellationToken) + { + var result = await mediator.Send(new ListStoreBusinessHoursQuery { StoreId = storeId }, cancellationToken); + return ApiResponse>.Ok(result); + } + + /// + /// 新增营业时段。 + /// + [HttpPost("{storeId:long}/business-hours")] + [PermissionAuthorize("store:update")] + [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] + public async Task> CreateBusinessHour(long storeId, [FromBody] CreateStoreBusinessHourCommand command, CancellationToken cancellationToken) + { + if (command.StoreId == 0) + { + command = command with { StoreId = storeId }; + } + + var result = await mediator.Send(command, cancellationToken); + return ApiResponse.Ok(result); + } + + /// + /// 更新营业时段。 + /// + [HttpPut("{storeId:long}/business-hours/{businessHourId:long}")] + [PermissionAuthorize("store:update")] + [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] + public async Task> UpdateBusinessHour(long storeId, long businessHourId, [FromBody] UpdateStoreBusinessHourCommand command, CancellationToken cancellationToken) + { + if (command.StoreId == 0 || command.BusinessHourId == 0) + { + command = command with { StoreId = storeId, BusinessHourId = businessHourId }; + } + + var result = await mediator.Send(command, cancellationToken); + return result == null + ? ApiResponse.Error(ErrorCodes.NotFound, "营业时段不存在") + : ApiResponse.Ok(result); + } + + /// + /// 删除营业时段。 + /// + [HttpDelete("{storeId:long}/business-hours/{businessHourId:long}")] + [PermissionAuthorize("store:update")] + [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] + public async Task> DeleteBusinessHour(long storeId, long businessHourId, CancellationToken cancellationToken) + { + var success = await mediator.Send(new DeleteStoreBusinessHourCommand { StoreId = storeId, BusinessHourId = businessHourId }, cancellationToken); + return success + ? ApiResponse.Ok(null) + : ApiResponse.Error(ErrorCodes.NotFound, "营业时段不存在"); + } + + /// + /// 查询配送区域。 + /// + [HttpGet("{storeId:long}/delivery-zones")] + [PermissionAuthorize("store:read")] + [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] + public async Task>> ListDeliveryZones(long storeId, CancellationToken cancellationToken) + { + var result = await mediator.Send(new ListStoreDeliveryZonesQuery { StoreId = storeId }, cancellationToken); + return ApiResponse>.Ok(result); + } + + /// + /// 新增配送区域。 + /// + [HttpPost("{storeId:long}/delivery-zones")] + [PermissionAuthorize("store:update")] + [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] + public async Task> CreateDeliveryZone(long storeId, [FromBody] CreateStoreDeliveryZoneCommand command, CancellationToken cancellationToken) + { + if (command.StoreId == 0) + { + command = command with { StoreId = storeId }; + } + + var result = await mediator.Send(command, cancellationToken); + return ApiResponse.Ok(result); + } + + /// + /// 更新配送区域。 + /// + [HttpPut("{storeId:long}/delivery-zones/{deliveryZoneId:long}")] + [PermissionAuthorize("store:update")] + [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] + public async Task> UpdateDeliveryZone(long storeId, long deliveryZoneId, [FromBody] UpdateStoreDeliveryZoneCommand command, CancellationToken cancellationToken) + { + if (command.StoreId == 0 || command.DeliveryZoneId == 0) + { + command = command with { StoreId = storeId, DeliveryZoneId = deliveryZoneId }; + } + + var result = await mediator.Send(command, cancellationToken); + return result == null + ? ApiResponse.Error(ErrorCodes.NotFound, "配送区域不存在") + : ApiResponse.Ok(result); + } + + /// + /// 删除配送区域。 + /// + [HttpDelete("{storeId:long}/delivery-zones/{deliveryZoneId:long}")] + [PermissionAuthorize("store:update")] + [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] + public async Task> DeleteDeliveryZone(long storeId, long deliveryZoneId, CancellationToken cancellationToken) + { + var success = await mediator.Send(new DeleteStoreDeliveryZoneCommand { StoreId = storeId, DeliveryZoneId = deliveryZoneId }, cancellationToken); + return success + ? ApiResponse.Ok(null) + : ApiResponse.Error(ErrorCodes.NotFound, "配送区域不存在"); + } + + /// + /// 查询门店节假日。 + /// + [HttpGet("{storeId:long}/holidays")] + [PermissionAuthorize("store:read")] + [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] + public async Task>> ListHolidays(long storeId, CancellationToken cancellationToken) + { + var result = await mediator.Send(new ListStoreHolidaysQuery { StoreId = storeId }, cancellationToken); + return ApiResponse>.Ok(result); + } + + /// + /// 新增节假日配置。 + /// + [HttpPost("{storeId:long}/holidays")] + [PermissionAuthorize("store:update")] + [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] + public async Task> CreateHoliday(long storeId, [FromBody] CreateStoreHolidayCommand command, CancellationToken cancellationToken) + { + if (command.StoreId == 0) + { + command = command with { StoreId = storeId }; + } + + var result = await mediator.Send(command, cancellationToken); + return ApiResponse.Ok(result); + } + + /// + /// 更新节假日配置。 + /// + [HttpPut("{storeId:long}/holidays/{holidayId:long}")] + [PermissionAuthorize("store:update")] + [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] + public async Task> UpdateHoliday(long storeId, long holidayId, [FromBody] UpdateStoreHolidayCommand command, CancellationToken cancellationToken) + { + if (command.StoreId == 0 || command.HolidayId == 0) + { + command = command with { StoreId = storeId, HolidayId = holidayId }; + } + + var result = await mediator.Send(command, cancellationToken); + return result == null + ? ApiResponse.Error(ErrorCodes.NotFound, "节假日配置不存在") + : ApiResponse.Ok(result); + } + + /// + /// 删除节假日配置。 + /// + [HttpDelete("{storeId:long}/holidays/{holidayId:long}")] + [PermissionAuthorize("store:update")] + [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] + public async Task> DeleteHoliday(long storeId, long holidayId, CancellationToken cancellationToken) + { + var success = await mediator.Send(new DeleteStoreHolidayCommand { StoreId = storeId, HolidayId = holidayId }, cancellationToken); + return success + ? ApiResponse.Ok(null) + : ApiResponse.Error(ErrorCodes.NotFound, "节假日配置不存在"); + } } diff --git a/src/Application/TakeoutSaaS.Application/App/Stores/Commands/CreateStoreBusinessHourCommand.cs b/src/Application/TakeoutSaaS.Application/App/Stores/Commands/CreateStoreBusinessHourCommand.cs new file mode 100644 index 0000000..345e990 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Stores/Commands/CreateStoreBusinessHourCommand.cs @@ -0,0 +1,46 @@ +using MediatR; +using TakeoutSaaS.Application.App.Stores.Dto; +using TakeoutSaaS.Domain.Stores.Enums; + +namespace TakeoutSaaS.Application.App.Stores.Commands; + +/// +/// 创建营业时段命令。 +/// +public sealed record CreateStoreBusinessHourCommand : IRequest +{ + /// + /// 门店 ID。 + /// + public long StoreId { get; init; } + + /// + /// 星期几。 + /// + public DayOfWeek DayOfWeek { get; init; } + + /// + /// 时段类型。 + /// + public BusinessHourType HourType { get; init; } = BusinessHourType.Normal; + + /// + /// 开始时间。 + /// + public TimeSpan StartTime { get; init; } + + /// + /// 结束时间。 + /// + public TimeSpan EndTime { get; init; } + + /// + /// 容量限制。 + /// + public int? CapacityLimit { get; init; } + + /// + /// 备注。 + /// + public string? Notes { get; init; } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Stores/Commands/CreateStoreCommand.cs b/src/Application/TakeoutSaaS.Application/App/Stores/Commands/CreateStoreCommand.cs index 21eb9c5..3315f92 100644 --- a/src/Application/TakeoutSaaS.Application/App/Stores/Commands/CreateStoreCommand.cs +++ b/src/Application/TakeoutSaaS.Application/App/Stores/Commands/CreateStoreCommand.cs @@ -98,4 +98,14 @@ public sealed class CreateStoreCommand : IRequest /// 支持配送。 /// public bool SupportsDelivery { get; set; } = true; + + /// + /// 支持预约。 + /// + public bool SupportsReservation { get; set; } + + /// + /// 支持排队叫号。 + /// + public bool SupportsQueueing { get; set; } } diff --git a/src/Application/TakeoutSaaS.Application/App/Stores/Commands/CreateStoreDeliveryZoneCommand.cs b/src/Application/TakeoutSaaS.Application/App/Stores/Commands/CreateStoreDeliveryZoneCommand.cs new file mode 100644 index 0000000..af1af08 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Stores/Commands/CreateStoreDeliveryZoneCommand.cs @@ -0,0 +1,45 @@ +using MediatR; +using TakeoutSaaS.Application.App.Stores.Dto; + +namespace TakeoutSaaS.Application.App.Stores.Commands; + +/// +/// 创建配送区域命令。 +/// +public sealed record CreateStoreDeliveryZoneCommand : IRequest +{ + /// + /// 门店 ID。 + /// + public long StoreId { get; init; } + + /// + /// 区域名称。 + /// + public string ZoneName { get; init; } = string.Empty; + + /// + /// GeoJSON。 + /// + public string PolygonGeoJson { get; init; } = string.Empty; + + /// + /// 起送价。 + /// + public decimal? MinimumOrderAmount { get; init; } + + /// + /// 配送费。 + /// + public decimal? DeliveryFee { get; init; } + + /// + /// 预计分钟。 + /// + public int? EstimatedMinutes { get; init; } + + /// + /// 排序。 + /// + public int SortOrder { get; init; } = 100; +} diff --git a/src/Application/TakeoutSaaS.Application/App/Stores/Commands/CreateStoreHolidayCommand.cs b/src/Application/TakeoutSaaS.Application/App/Stores/Commands/CreateStoreHolidayCommand.cs new file mode 100644 index 0000000..896c090 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Stores/Commands/CreateStoreHolidayCommand.cs @@ -0,0 +1,30 @@ +using MediatR; +using TakeoutSaaS.Application.App.Stores.Dto; + +namespace TakeoutSaaS.Application.App.Stores.Commands; + +/// +/// 创建节假日配置命令。 +/// +public sealed record CreateStoreHolidayCommand : IRequest +{ + /// + /// 门店 ID。 + /// + public long StoreId { get; init; } + + /// + /// 日期。 + /// + public DateTime Date { get; init; } + + /// + /// 是否闭店。 + /// + public bool IsClosed { get; init; } = true; + + /// + /// 说明。 + /// + public string? Reason { get; init; } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Stores/Commands/DeleteStoreBusinessHourCommand.cs b/src/Application/TakeoutSaaS.Application/App/Stores/Commands/DeleteStoreBusinessHourCommand.cs new file mode 100644 index 0000000..d101680 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Stores/Commands/DeleteStoreBusinessHourCommand.cs @@ -0,0 +1,19 @@ +using MediatR; + +namespace TakeoutSaaS.Application.App.Stores.Commands; + +/// +/// 删除营业时段命令。 +/// +public sealed record DeleteStoreBusinessHourCommand : IRequest +{ + /// + /// 门店 ID。 + /// + public long StoreId { get; init; } + + /// + /// 营业时段 ID。 + /// + public long BusinessHourId { get; init; } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Stores/Commands/DeleteStoreDeliveryZoneCommand.cs b/src/Application/TakeoutSaaS.Application/App/Stores/Commands/DeleteStoreDeliveryZoneCommand.cs new file mode 100644 index 0000000..0e9e57d --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Stores/Commands/DeleteStoreDeliveryZoneCommand.cs @@ -0,0 +1,19 @@ +using MediatR; + +namespace TakeoutSaaS.Application.App.Stores.Commands; + +/// +/// 删除配送区域命令。 +/// +public sealed record DeleteStoreDeliveryZoneCommand : IRequest +{ + /// + /// 门店 ID。 + /// + public long StoreId { get; init; } + + /// + /// 配送区域 ID。 + /// + public long DeliveryZoneId { get; init; } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Stores/Commands/DeleteStoreHolidayCommand.cs b/src/Application/TakeoutSaaS.Application/App/Stores/Commands/DeleteStoreHolidayCommand.cs new file mode 100644 index 0000000..8e86eb8 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Stores/Commands/DeleteStoreHolidayCommand.cs @@ -0,0 +1,19 @@ +using MediatR; + +namespace TakeoutSaaS.Application.App.Stores.Commands; + +/// +/// 删除节假日配置命令。 +/// +public sealed record DeleteStoreHolidayCommand : IRequest +{ + /// + /// 门店 ID。 + /// + public long StoreId { get; init; } + + /// + /// 节假日 ID。 + /// + public long HolidayId { get; init; } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Stores/Commands/UpdateStoreBusinessHourCommand.cs b/src/Application/TakeoutSaaS.Application/App/Stores/Commands/UpdateStoreBusinessHourCommand.cs new file mode 100644 index 0000000..7c13e2e --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Stores/Commands/UpdateStoreBusinessHourCommand.cs @@ -0,0 +1,51 @@ +using MediatR; +using TakeoutSaaS.Application.App.Stores.Dto; +using TakeoutSaaS.Domain.Stores.Enums; + +namespace TakeoutSaaS.Application.App.Stores.Commands; + +/// +/// 更新营业时段命令。 +/// +public sealed record UpdateStoreBusinessHourCommand : IRequest +{ + /// + /// 营业时段 ID。 + /// + public long BusinessHourId { get; init; } + + /// + /// 门店 ID。 + /// + public long StoreId { get; init; } + + /// + /// 星期几。 + /// + public DayOfWeek DayOfWeek { get; init; } + + /// + /// 时段类型。 + /// + public BusinessHourType HourType { get; init; } = BusinessHourType.Normal; + + /// + /// 开始时间。 + /// + public TimeSpan StartTime { get; init; } + + /// + /// 结束时间。 + /// + public TimeSpan EndTime { get; init; } + + /// + /// 容量限制。 + /// + public int? CapacityLimit { get; init; } + + /// + /// 备注。 + /// + public string? Notes { get; init; } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Stores/Commands/UpdateStoreCommand.cs b/src/Application/TakeoutSaaS.Application/App/Stores/Commands/UpdateStoreCommand.cs index 63a178d..c7ef3b5 100644 --- a/src/Application/TakeoutSaaS.Application/App/Stores/Commands/UpdateStoreCommand.cs +++ b/src/Application/TakeoutSaaS.Application/App/Stores/Commands/UpdateStoreCommand.cs @@ -103,4 +103,14 @@ public sealed record UpdateStoreCommand : IRequest /// 支持配送。 /// public bool SupportsDelivery { get; init; } = true; + + /// + /// 支持预约。 + /// + public bool SupportsReservation { get; init; } + + /// + /// 支持排队叫号。 + /// + public bool SupportsQueueing { get; init; } } diff --git a/src/Application/TakeoutSaaS.Application/App/Stores/Commands/UpdateStoreDeliveryZoneCommand.cs b/src/Application/TakeoutSaaS.Application/App/Stores/Commands/UpdateStoreDeliveryZoneCommand.cs new file mode 100644 index 0000000..e21ded5 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Stores/Commands/UpdateStoreDeliveryZoneCommand.cs @@ -0,0 +1,50 @@ +using MediatR; +using TakeoutSaaS.Application.App.Stores.Dto; + +namespace TakeoutSaaS.Application.App.Stores.Commands; + +/// +/// 更新配送区域命令。 +/// +public sealed record UpdateStoreDeliveryZoneCommand : IRequest +{ + /// + /// 配送区域 ID。 + /// + public long DeliveryZoneId { get; init; } + + /// + /// 门店 ID。 + /// + public long StoreId { get; init; } + + /// + /// 区域名称。 + /// + public string ZoneName { get; init; } = string.Empty; + + /// + /// GeoJSON。 + /// + public string PolygonGeoJson { get; init; } = string.Empty; + + /// + /// 起送价。 + /// + public decimal? MinimumOrderAmount { get; init; } + + /// + /// 配送费。 + /// + public decimal? DeliveryFee { get; init; } + + /// + /// 预计分钟。 + /// + public int? EstimatedMinutes { get; init; } + + /// + /// 排序。 + /// + public int SortOrder { get; init; } = 100; +} diff --git a/src/Application/TakeoutSaaS.Application/App/Stores/Commands/UpdateStoreHolidayCommand.cs b/src/Application/TakeoutSaaS.Application/App/Stores/Commands/UpdateStoreHolidayCommand.cs new file mode 100644 index 0000000..c228bb1 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Stores/Commands/UpdateStoreHolidayCommand.cs @@ -0,0 +1,35 @@ +using MediatR; +using TakeoutSaaS.Application.App.Stores.Dto; + +namespace TakeoutSaaS.Application.App.Stores.Commands; + +/// +/// 更新节假日配置命令。 +/// +public sealed record UpdateStoreHolidayCommand : IRequest +{ + /// + /// 节假日 ID。 + /// + public long HolidayId { get; init; } + + /// + /// 门店 ID。 + /// + public long StoreId { get; init; } + + /// + /// 日期。 + /// + public DateTime Date { get; init; } + + /// + /// 是否闭店。 + /// + public bool IsClosed { get; init; } = true; + + /// + /// 说明。 + /// + public string? Reason { get; init; } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Stores/Dto/StoreBusinessHourDto.cs b/src/Application/TakeoutSaaS.Application/App/Stores/Dto/StoreBusinessHourDto.cs new file mode 100644 index 0000000..0a28ebd --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Stores/Dto/StoreBusinessHourDto.cs @@ -0,0 +1,64 @@ +using System.Text.Json.Serialization; +using TakeoutSaaS.Domain.Stores.Enums; +using TakeoutSaaS.Shared.Abstractions.Serialization; + +namespace TakeoutSaaS.Application.App.Stores.Dto; + +/// +/// 门店营业时段 DTO。 +/// +public sealed record StoreBusinessHourDto +{ + /// + /// 营业时段 ID。 + /// + [JsonConverter(typeof(SnowflakeIdJsonConverter))] + public long Id { get; init; } + + /// + /// 租户 ID。 + /// + [JsonConverter(typeof(SnowflakeIdJsonConverter))] + public long TenantId { get; init; } + + /// + /// 门店 ID。 + /// + [JsonConverter(typeof(SnowflakeIdJsonConverter))] + public long StoreId { get; init; } + + /// + /// 星期几。 + /// + public DayOfWeek DayOfWeek { get; init; } + + /// + /// 时段类型。 + /// + public BusinessHourType HourType { get; init; } + + /// + /// 开始时间。 + /// + public TimeSpan StartTime { get; init; } + + /// + /// 结束时间。 + /// + public TimeSpan EndTime { get; init; } + + /// + /// 容量限制。 + /// + public int? CapacityLimit { get; init; } + + /// + /// 备注。 + /// + public string? Notes { get; init; } + + /// + /// 创建时间。 + /// + public DateTime CreatedAt { get; init; } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Stores/Dto/StoreDeliveryZoneDto.cs b/src/Application/TakeoutSaaS.Application/App/Stores/Dto/StoreDeliveryZoneDto.cs new file mode 100644 index 0000000..588aa43 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Stores/Dto/StoreDeliveryZoneDto.cs @@ -0,0 +1,63 @@ +using System.Text.Json.Serialization; +using TakeoutSaaS.Shared.Abstractions.Serialization; + +namespace TakeoutSaaS.Application.App.Stores.Dto; + +/// +/// 门店配送区域 DTO。 +/// +public sealed record StoreDeliveryZoneDto +{ + /// + /// 配送区域 ID。 + /// + [JsonConverter(typeof(SnowflakeIdJsonConverter))] + public long Id { get; init; } + + /// + /// 租户 ID。 + /// + [JsonConverter(typeof(SnowflakeIdJsonConverter))] + public long TenantId { get; init; } + + /// + /// 门店 ID。 + /// + [JsonConverter(typeof(SnowflakeIdJsonConverter))] + public long StoreId { get; init; } + + /// + /// 区域名称。 + /// + public string ZoneName { get; init; } = string.Empty; + + /// + /// GeoJSON。 + /// + public string PolygonGeoJson { get; init; } = string.Empty; + + /// + /// 起送价。 + /// + public decimal? MinimumOrderAmount { get; init; } + + /// + /// 配送费。 + /// + public decimal? DeliveryFee { get; init; } + + /// + /// 预计分钟。 + /// + public int? EstimatedMinutes { get; init; } + + /// + /// 排序。 + /// + public int SortOrder { get; init; } + + /// + /// 创建时间。 + /// + public DateTime CreatedAt { get; init; } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Stores/Dto/StoreDto.cs b/src/Application/TakeoutSaaS.Application/App/Stores/Dto/StoreDto.cs index 5412717..8cf3cc0 100644 --- a/src/Application/TakeoutSaaS.Application/App/Stores/Dto/StoreDto.cs +++ b/src/Application/TakeoutSaaS.Application/App/Stores/Dto/StoreDto.cs @@ -112,6 +112,16 @@ public sealed class StoreDto /// public bool SupportsDelivery { get; init; } + /// + /// 支持预约。 + /// + public bool SupportsReservation { get; init; } + + /// + /// 支持排队叫号。 + /// + public bool SupportsQueueing { get; init; } + /// /// 创建时间。 /// diff --git a/src/Application/TakeoutSaaS.Application/App/Stores/Dto/StoreHolidayDto.cs b/src/Application/TakeoutSaaS.Application/App/Stores/Dto/StoreHolidayDto.cs new file mode 100644 index 0000000..a861b85 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Stores/Dto/StoreHolidayDto.cs @@ -0,0 +1,48 @@ +using System.Text.Json.Serialization; +using TakeoutSaaS.Shared.Abstractions.Serialization; + +namespace TakeoutSaaS.Application.App.Stores.Dto; + +/// +/// 门店节假日 DTO。 +/// +public sealed record StoreHolidayDto +{ + /// + /// 节假日 ID。 + /// + [JsonConverter(typeof(SnowflakeIdJsonConverter))] + public long Id { get; init; } + + /// + /// 租户 ID。 + /// + [JsonConverter(typeof(SnowflakeIdJsonConverter))] + public long TenantId { get; init; } + + /// + /// 门店 ID。 + /// + [JsonConverter(typeof(SnowflakeIdJsonConverter))] + public long StoreId { get; init; } + + /// + /// 日期。 + /// + public DateTime Date { get; init; } + + /// + /// 是否闭店。 + /// + public bool IsClosed { get; init; } + + /// + /// 说明。 + /// + public string? Reason { get; init; } + + /// + /// 创建时间。 + /// + public DateTime CreatedAt { get; init; } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/CreateStoreBusinessHourCommandHandler.cs b/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/CreateStoreBusinessHourCommandHandler.cs new file mode 100644 index 0000000..9b474b3 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/CreateStoreBusinessHourCommandHandler.cs @@ -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.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; + +/// +/// 创建营业时段处理器。 +/// +public sealed class CreateStoreBusinessHourCommandHandler( + IStoreRepository storeRepository, + ITenantProvider tenantProvider, + ILogger logger) + : IRequestHandler +{ + private readonly IStoreRepository _storeRepository = storeRepository; + private readonly ITenantProvider _tenantProvider = tenantProvider; + private readonly ILogger _logger = logger; + + /// + public async Task Handle(CreateStoreBusinessHourCommand 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 hour = new StoreBusinessHour + { + StoreId = request.StoreId, + DayOfWeek = request.DayOfWeek, + HourType = request.HourType, + StartTime = request.StartTime, + EndTime = request.EndTime, + CapacityLimit = request.CapacityLimit, + Notes = request.Notes?.Trim() + }; + + // 3. 持久化 + await _storeRepository.AddBusinessHoursAsync(new[] { hour }, cancellationToken); + await _storeRepository.SaveChangesAsync(cancellationToken); + _logger.LogInformation("创建营业时段 {BusinessHourId} 对应门店 {StoreId}", hour.Id, request.StoreId); + + // 4. 返回 DTO + return StoreMapping.ToDto(hour); + } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/CreateStoreCommandHandler.cs b/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/CreateStoreCommandHandler.cs index f254d6f..104c302 100644 --- a/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/CreateStoreCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/CreateStoreCommandHandler.cs @@ -39,7 +39,9 @@ public sealed class CreateStoreCommandHandler(IStoreRepository storeRepository, DeliveryRadiusKm = request.DeliveryRadiusKm, SupportsDineIn = request.SupportsDineIn, SupportsPickup = request.SupportsPickup, - SupportsDelivery = request.SupportsDelivery + SupportsDelivery = request.SupportsDelivery, + SupportsReservation = request.SupportsReservation, + SupportsQueueing = request.SupportsQueueing }; // 2. 持久化 @@ -73,6 +75,8 @@ public sealed class CreateStoreCommandHandler(IStoreRepository storeRepository, SupportsDineIn = store.SupportsDineIn, SupportsPickup = store.SupportsPickup, SupportsDelivery = store.SupportsDelivery, + SupportsReservation = store.SupportsReservation, + SupportsQueueing = store.SupportsQueueing, CreatedAt = store.CreatedAt }; } diff --git a/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/CreateStoreDeliveryZoneCommandHandler.cs b/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/CreateStoreDeliveryZoneCommandHandler.cs new file mode 100644 index 0000000..c5858b3 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/CreateStoreDeliveryZoneCommandHandler.cs @@ -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.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; + +/// +/// 创建配送区域处理器。 +/// +public sealed class CreateStoreDeliveryZoneCommandHandler( + IStoreRepository storeRepository, + ITenantProvider tenantProvider, + ILogger logger) + : IRequestHandler +{ + private readonly IStoreRepository _storeRepository = storeRepository; + private readonly ITenantProvider _tenantProvider = tenantProvider; + private readonly ILogger _logger = logger; + + /// + public async Task Handle(CreateStoreDeliveryZoneCommand 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 zone = new StoreDeliveryZone + { + StoreId = request.StoreId, + ZoneName = request.ZoneName.Trim(), + PolygonGeoJson = request.PolygonGeoJson.Trim(), + MinimumOrderAmount = request.MinimumOrderAmount, + DeliveryFee = request.DeliveryFee, + EstimatedMinutes = request.EstimatedMinutes, + SortOrder = request.SortOrder + }; + + // 3. 持久化 + await _storeRepository.AddDeliveryZonesAsync(new[] { zone }, cancellationToken); + await _storeRepository.SaveChangesAsync(cancellationToken); + _logger.LogInformation("创建配送区域 {DeliveryZoneId} 对应门店 {StoreId}", zone.Id, request.StoreId); + + // 4. 返回 DTO + return StoreMapping.ToDto(zone); + } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/CreateStoreHolidayCommandHandler.cs b/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/CreateStoreHolidayCommandHandler.cs new file mode 100644 index 0000000..f5f5692 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/CreateStoreHolidayCommandHandler.cs @@ -0,0 +1,54 @@ +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; + +/// +/// 创建节假日配置处理器。 +/// +public sealed class CreateStoreHolidayCommandHandler( + IStoreRepository storeRepository, + ITenantProvider tenantProvider, + ILogger logger) + : IRequestHandler +{ + private readonly IStoreRepository _storeRepository = storeRepository; + private readonly ITenantProvider _tenantProvider = tenantProvider; + private readonly ILogger _logger = logger; + + /// + public async Task Handle(CreateStoreHolidayCommand 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 holiday = new StoreHoliday + { + StoreId = request.StoreId, + Date = request.Date, + IsClosed = request.IsClosed, + Reason = request.Reason?.Trim() + }; + + // 3. 持久化 + await _storeRepository.AddHolidaysAsync(new[] { holiday }, cancellationToken); + await _storeRepository.SaveChangesAsync(cancellationToken); + _logger.LogInformation("创建节假日 {HolidayId} 对应门店 {StoreId}", holiday.Id, request.StoreId); + + // 4. 返回 DTO + return StoreMapping.ToDto(holiday); + } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/DeleteStoreBusinessHourCommandHandler.cs b/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/DeleteStoreBusinessHourCommandHandler.cs new file mode 100644 index 0000000..816cd88 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/DeleteStoreBusinessHourCommandHandler.cs @@ -0,0 +1,46 @@ +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; + +/// +/// 删除营业时段处理器。 +/// +public sealed class DeleteStoreBusinessHourCommandHandler( + IStoreRepository storeRepository, + ITenantProvider tenantProvider, + ILogger logger) + : IRequestHandler +{ + private readonly IStoreRepository _storeRepository = storeRepository; + private readonly ITenantProvider _tenantProvider = tenantProvider; + private readonly ILogger _logger = logger; + + /// + public async Task Handle(DeleteStoreBusinessHourCommand request, CancellationToken cancellationToken) + { + // 1. 读取时段 + var tenantId = _tenantProvider.GetCurrentTenantId(); + var existing = await _storeRepository.FindBusinessHourByIdAsync(request.BusinessHourId, tenantId, cancellationToken); + if (existing is null) + { + return false; + } + + // 2. 校验门店归属 + if (existing.StoreId != request.StoreId) + { + return false; + } + + // 3. 删除 + await _storeRepository.DeleteBusinessHourAsync(request.BusinessHourId, tenantId, cancellationToken); + await _storeRepository.SaveChangesAsync(cancellationToken); + _logger.LogInformation("删除营业时段 {BusinessHourId} 对应门店 {StoreId}", request.BusinessHourId, request.StoreId); + + return true; + } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/DeleteStoreDeliveryZoneCommandHandler.cs b/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/DeleteStoreDeliveryZoneCommandHandler.cs new file mode 100644 index 0000000..c7e7abb --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/DeleteStoreDeliveryZoneCommandHandler.cs @@ -0,0 +1,46 @@ +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; + +/// +/// 删除配送区域处理器。 +/// +public sealed class DeleteStoreDeliveryZoneCommandHandler( + IStoreRepository storeRepository, + ITenantProvider tenantProvider, + ILogger logger) + : IRequestHandler +{ + private readonly IStoreRepository _storeRepository = storeRepository; + private readonly ITenantProvider _tenantProvider = tenantProvider; + private readonly ILogger _logger = logger; + + /// + public async Task Handle(DeleteStoreDeliveryZoneCommand request, CancellationToken cancellationToken) + { + // 1. 读取区域 + var tenantId = _tenantProvider.GetCurrentTenantId(); + var existing = await _storeRepository.FindDeliveryZoneByIdAsync(request.DeliveryZoneId, tenantId, cancellationToken); + if (existing is null) + { + return false; + } + + // 2. 校验门店归属 + if (existing.StoreId != request.StoreId) + { + return false; + } + + // 3. 删除 + await _storeRepository.DeleteDeliveryZoneAsync(request.DeliveryZoneId, tenantId, cancellationToken); + await _storeRepository.SaveChangesAsync(cancellationToken); + _logger.LogInformation("删除配送区域 {DeliveryZoneId} 对应门店 {StoreId}", request.DeliveryZoneId, request.StoreId); + + return true; + } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/DeleteStoreHolidayCommandHandler.cs b/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/DeleteStoreHolidayCommandHandler.cs new file mode 100644 index 0000000..a262ffe --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/DeleteStoreHolidayCommandHandler.cs @@ -0,0 +1,46 @@ +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; + +/// +/// 删除节假日配置处理器。 +/// +public sealed class DeleteStoreHolidayCommandHandler( + IStoreRepository storeRepository, + ITenantProvider tenantProvider, + ILogger logger) + : IRequestHandler +{ + private readonly IStoreRepository _storeRepository = storeRepository; + private readonly ITenantProvider _tenantProvider = tenantProvider; + private readonly ILogger _logger = logger; + + /// + public async Task Handle(DeleteStoreHolidayCommand request, CancellationToken cancellationToken) + { + // 1. 读取配置 + var tenantId = _tenantProvider.GetCurrentTenantId(); + var existing = await _storeRepository.FindHolidayByIdAsync(request.HolidayId, tenantId, cancellationToken); + if (existing is null) + { + return false; + } + + // 2. 校验门店归属 + if (existing.StoreId != request.StoreId) + { + return false; + } + + // 3. 删除 + await _storeRepository.DeleteHolidayAsync(request.HolidayId, tenantId, cancellationToken); + await _storeRepository.SaveChangesAsync(cancellationToken); + _logger.LogInformation("删除节假日 {HolidayId} 对应门店 {StoreId}", request.HolidayId, request.StoreId); + + return true; + } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/GetStoreByIdQueryHandler.cs b/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/GetStoreByIdQueryHandler.cs index 995ddde..ecd7415 100644 --- a/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/GetStoreByIdQueryHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/GetStoreByIdQueryHandler.cs @@ -48,6 +48,8 @@ public sealed class GetStoreByIdQueryHandler( SupportsDineIn = store.SupportsDineIn, SupportsPickup = store.SupportsPickup, SupportsDelivery = store.SupportsDelivery, + SupportsReservation = store.SupportsReservation, + SupportsQueueing = store.SupportsQueueing, CreatedAt = store.CreatedAt }; } diff --git a/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/ListStoreBusinessHoursQueryHandler.cs b/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/ListStoreBusinessHoursQueryHandler.cs new file mode 100644 index 0000000..f0f2c5d --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/ListStoreBusinessHoursQueryHandler.cs @@ -0,0 +1,31 @@ +using System.Linq; +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; + +/// +/// 营业时段列表查询处理器。 +/// +public sealed class ListStoreBusinessHoursQueryHandler( + IStoreRepository storeRepository, + ITenantProvider tenantProvider) + : IRequestHandler> +{ + private readonly IStoreRepository _storeRepository = storeRepository; + private readonly ITenantProvider _tenantProvider = tenantProvider; + + /// + public async Task> Handle(ListStoreBusinessHoursQuery request, CancellationToken cancellationToken) + { + // 1. 查询时段列表 + var tenantId = _tenantProvider.GetCurrentTenantId(); + var hours = await _storeRepository.GetBusinessHoursAsync(request.StoreId, tenantId, cancellationToken); + + // 2. 映射 DTO + return hours.Select(StoreMapping.ToDto).ToList(); + } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/ListStoreDeliveryZonesQueryHandler.cs b/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/ListStoreDeliveryZonesQueryHandler.cs new file mode 100644 index 0000000..ffcc741 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/ListStoreDeliveryZonesQueryHandler.cs @@ -0,0 +1,31 @@ +using System.Linq; +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; + +/// +/// 配送区域列表查询处理器。 +/// +public sealed class ListStoreDeliveryZonesQueryHandler( + IStoreRepository storeRepository, + ITenantProvider tenantProvider) + : IRequestHandler> +{ + private readonly IStoreRepository _storeRepository = storeRepository; + private readonly ITenantProvider _tenantProvider = tenantProvider; + + /// + public async Task> Handle(ListStoreDeliveryZonesQuery request, CancellationToken cancellationToken) + { + // 1. 查询配送区域 + var tenantId = _tenantProvider.GetCurrentTenantId(); + var zones = await _storeRepository.GetDeliveryZonesAsync(request.StoreId, tenantId, cancellationToken); + + // 2. 映射 DTO + return zones.Select(StoreMapping.ToDto).ToList(); + } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/ListStoreHolidaysQueryHandler.cs b/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/ListStoreHolidaysQueryHandler.cs new file mode 100644 index 0000000..558b8f2 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/ListStoreHolidaysQueryHandler.cs @@ -0,0 +1,31 @@ +using System.Linq; +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; + +/// +/// 门店节假日列表查询处理器。 +/// +public sealed class ListStoreHolidaysQueryHandler( + IStoreRepository storeRepository, + ITenantProvider tenantProvider) + : IRequestHandler> +{ + private readonly IStoreRepository _storeRepository = storeRepository; + private readonly ITenantProvider _tenantProvider = tenantProvider; + + /// + public async Task> Handle(ListStoreHolidaysQuery request, CancellationToken cancellationToken) + { + // 1. 查询节假日 + var tenantId = _tenantProvider.GetCurrentTenantId(); + var holidays = await _storeRepository.GetHolidaysAsync(request.StoreId, tenantId, cancellationToken); + + // 2. 映射 DTO + return holidays.Select(StoreMapping.ToDto).ToList(); + } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/SearchStoresQueryHandler.cs b/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/SearchStoresQueryHandler.cs index 1bb609d..f9b2330 100644 --- a/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/SearchStoresQueryHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/SearchStoresQueryHandler.cs @@ -75,6 +75,8 @@ public sealed class SearchStoresQueryHandler( SupportsDineIn = store.SupportsDineIn, SupportsPickup = store.SupportsPickup, SupportsDelivery = store.SupportsDelivery, + SupportsReservation = store.SupportsReservation, + SupportsQueueing = store.SupportsQueueing, CreatedAt = store.CreatedAt }; } diff --git a/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/UpdateStoreBusinessHourCommandHandler.cs b/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/UpdateStoreBusinessHourCommandHandler.cs new file mode 100644 index 0000000..b85bd55 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/UpdateStoreBusinessHourCommandHandler.cs @@ -0,0 +1,59 @@ +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; + +/// +/// 更新营业时段处理器。 +/// +public sealed class UpdateStoreBusinessHourCommandHandler( + IStoreRepository storeRepository, + ITenantProvider tenantProvider, + ILogger logger) + : IRequestHandler +{ + private readonly IStoreRepository _storeRepository = storeRepository; + private readonly ITenantProvider _tenantProvider = tenantProvider; + private readonly ILogger _logger = logger; + + /// + public async Task Handle(UpdateStoreBusinessHourCommand request, CancellationToken cancellationToken) + { + // 1. 读取时段 + var tenantId = _tenantProvider.GetCurrentTenantId(); + var existing = await _storeRepository.FindBusinessHourByIdAsync(request.BusinessHourId, tenantId, cancellationToken); + if (existing is null) + { + return null; + } + + // 2. 校验门店归属 + if (existing.StoreId != request.StoreId) + { + throw new BusinessException(ErrorCodes.ValidationFailed, "营业时段不属于该门店"); + } + + // 3. 更新字段 + existing.DayOfWeek = request.DayOfWeek; + existing.HourType = request.HourType; + existing.StartTime = request.StartTime; + existing.EndTime = request.EndTime; + existing.CapacityLimit = request.CapacityLimit; + existing.Notes = request.Notes?.Trim(); + + // 4. 持久化 + await _storeRepository.UpdateBusinessHourAsync(existing, cancellationToken); + await _storeRepository.SaveChangesAsync(cancellationToken); + _logger.LogInformation("更新营业时段 {BusinessHourId} 对应门店 {StoreId}", existing.Id, existing.StoreId); + + // 5. 返回 DTO + return StoreMapping.ToDto(existing); + } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/UpdateStoreCommandHandler.cs b/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/UpdateStoreCommandHandler.cs index 934bb54..025ae4d 100644 --- a/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/UpdateStoreCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/UpdateStoreCommandHandler.cs @@ -51,6 +51,8 @@ public sealed class UpdateStoreCommandHandler( existing.SupportsDineIn = request.SupportsDineIn; existing.SupportsPickup = request.SupportsPickup; existing.SupportsDelivery = request.SupportsDelivery; + existing.SupportsReservation = request.SupportsReservation; + existing.SupportsQueueing = request.SupportsQueueing; // 3. 持久化 await _storeRepository.UpdateStoreAsync(existing, cancellationToken); @@ -83,6 +85,8 @@ public sealed class UpdateStoreCommandHandler( SupportsDineIn = store.SupportsDineIn, SupportsPickup = store.SupportsPickup, SupportsDelivery = store.SupportsDelivery, + SupportsReservation = store.SupportsReservation, + SupportsQueueing = store.SupportsQueueing, CreatedAt = store.CreatedAt }; } diff --git a/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/UpdateStoreDeliveryZoneCommandHandler.cs b/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/UpdateStoreDeliveryZoneCommandHandler.cs new file mode 100644 index 0000000..ecf7f66 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/UpdateStoreDeliveryZoneCommandHandler.cs @@ -0,0 +1,59 @@ +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; + +/// +/// 更新配送区域处理器。 +/// +public sealed class UpdateStoreDeliveryZoneCommandHandler( + IStoreRepository storeRepository, + ITenantProvider tenantProvider, + ILogger logger) + : IRequestHandler +{ + private readonly IStoreRepository _storeRepository = storeRepository; + private readonly ITenantProvider _tenantProvider = tenantProvider; + private readonly ILogger _logger = logger; + + /// + public async Task Handle(UpdateStoreDeliveryZoneCommand request, CancellationToken cancellationToken) + { + // 1. 读取区域 + var tenantId = _tenantProvider.GetCurrentTenantId(); + var existing = await _storeRepository.FindDeliveryZoneByIdAsync(request.DeliveryZoneId, tenantId, cancellationToken); + if (existing is null) + { + return null; + } + + // 2. 校验门店归属 + if (existing.StoreId != request.StoreId) + { + throw new BusinessException(ErrorCodes.ValidationFailed, "配送区域不属于该门店"); + } + + // 3. 更新字段 + existing.ZoneName = request.ZoneName.Trim(); + existing.PolygonGeoJson = request.PolygonGeoJson.Trim(); + existing.MinimumOrderAmount = request.MinimumOrderAmount; + existing.DeliveryFee = request.DeliveryFee; + existing.EstimatedMinutes = request.EstimatedMinutes; + existing.SortOrder = request.SortOrder; + + // 4. 持久化 + await _storeRepository.UpdateDeliveryZoneAsync(existing, cancellationToken); + await _storeRepository.SaveChangesAsync(cancellationToken); + _logger.LogInformation("更新配送区域 {DeliveryZoneId} 对应门店 {StoreId}", existing.Id, existing.StoreId); + + // 5. 返回 DTO + return StoreMapping.ToDto(existing); + } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/UpdateStoreHolidayCommandHandler.cs b/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/UpdateStoreHolidayCommandHandler.cs new file mode 100644 index 0000000..878eaeb --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/UpdateStoreHolidayCommandHandler.cs @@ -0,0 +1,56 @@ +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; + +/// +/// 更新节假日配置处理器。 +/// +public sealed class UpdateStoreHolidayCommandHandler( + IStoreRepository storeRepository, + ITenantProvider tenantProvider, + ILogger logger) + : IRequestHandler +{ + private readonly IStoreRepository _storeRepository = storeRepository; + private readonly ITenantProvider _tenantProvider = tenantProvider; + private readonly ILogger _logger = logger; + + /// + public async Task Handle(UpdateStoreHolidayCommand request, CancellationToken cancellationToken) + { + // 1. 读取配置 + var tenantId = _tenantProvider.GetCurrentTenantId(); + var existing = await _storeRepository.FindHolidayByIdAsync(request.HolidayId, tenantId, cancellationToken); + if (existing is null) + { + return null; + } + + // 2. 校验门店归属 + if (existing.StoreId != request.StoreId) + { + throw new BusinessException(ErrorCodes.ValidationFailed, "节假日配置不属于该门店"); + } + + // 3. 更新字段 + existing.Date = request.Date; + existing.IsClosed = request.IsClosed; + existing.Reason = request.Reason?.Trim(); + + // 4. 持久化 + await _storeRepository.UpdateHolidayAsync(existing, cancellationToken); + await _storeRepository.SaveChangesAsync(cancellationToken); + _logger.LogInformation("更新节假日 {HolidayId} 对应门店 {StoreId}", existing.Id, existing.StoreId); + + // 5. 返回 DTO + return StoreMapping.ToDto(existing); + } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Stores/Queries/ListStoreBusinessHoursQuery.cs b/src/Application/TakeoutSaaS.Application/App/Stores/Queries/ListStoreBusinessHoursQuery.cs new file mode 100644 index 0000000..dac381a --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Stores/Queries/ListStoreBusinessHoursQuery.cs @@ -0,0 +1,15 @@ +using MediatR; +using TakeoutSaaS.Application.App.Stores.Dto; + +namespace TakeoutSaaS.Application.App.Stores.Queries; + +/// +/// 营业时段列表查询。 +/// +public sealed record ListStoreBusinessHoursQuery : IRequest> +{ + /// + /// 门店 ID。 + /// + public long StoreId { get; init; } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Stores/Queries/ListStoreDeliveryZonesQuery.cs b/src/Application/TakeoutSaaS.Application/App/Stores/Queries/ListStoreDeliveryZonesQuery.cs new file mode 100644 index 0000000..c3ed9ed --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Stores/Queries/ListStoreDeliveryZonesQuery.cs @@ -0,0 +1,15 @@ +using MediatR; +using TakeoutSaaS.Application.App.Stores.Dto; + +namespace TakeoutSaaS.Application.App.Stores.Queries; + +/// +/// 配送区域列表查询。 +/// +public sealed record ListStoreDeliveryZonesQuery : IRequest> +{ + /// + /// 门店 ID。 + /// + public long StoreId { get; init; } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Stores/Queries/ListStoreHolidaysQuery.cs b/src/Application/TakeoutSaaS.Application/App/Stores/Queries/ListStoreHolidaysQuery.cs new file mode 100644 index 0000000..bf14cd2 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Stores/Queries/ListStoreHolidaysQuery.cs @@ -0,0 +1,15 @@ +using MediatR; +using TakeoutSaaS.Application.App.Stores.Dto; + +namespace TakeoutSaaS.Application.App.Stores.Queries; + +/// +/// 门店节假日列表查询。 +/// +public sealed record ListStoreHolidaysQuery : IRequest> +{ + /// + /// 门店 ID。 + /// + public long StoreId { get; init; } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Stores/StoreMapping.cs b/src/Application/TakeoutSaaS.Application/App/Stores/StoreMapping.cs new file mode 100644 index 0000000..06c9094 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Stores/StoreMapping.cs @@ -0,0 +1,64 @@ +using TakeoutSaaS.Application.App.Stores.Dto; +using TakeoutSaaS.Domain.Stores.Entities; + +namespace TakeoutSaaS.Application.App.Stores; + +/// +/// 门店相关映射助手。 +/// +public static class StoreMapping +{ + /// + /// 映射营业时段 DTO。 + /// + /// 营业时段实体。 + /// DTO。 + public static StoreBusinessHourDto ToDto(StoreBusinessHour hour) => new() + { + Id = hour.Id, + TenantId = hour.TenantId, + StoreId = hour.StoreId, + DayOfWeek = hour.DayOfWeek, + HourType = hour.HourType, + StartTime = hour.StartTime, + EndTime = hour.EndTime, + CapacityLimit = hour.CapacityLimit, + Notes = hour.Notes, + CreatedAt = hour.CreatedAt + }; + + /// + /// 映射配送区域 DTO。 + /// + /// 配送区域实体。 + /// DTO。 + public static StoreDeliveryZoneDto ToDto(StoreDeliveryZone zone) => new() + { + Id = zone.Id, + TenantId = zone.TenantId, + StoreId = zone.StoreId, + ZoneName = zone.ZoneName, + PolygonGeoJson = zone.PolygonGeoJson, + MinimumOrderAmount = zone.MinimumOrderAmount, + DeliveryFee = zone.DeliveryFee, + EstimatedMinutes = zone.EstimatedMinutes, + SortOrder = zone.SortOrder, + CreatedAt = zone.CreatedAt + }; + + /// + /// 映射节假日 DTO。 + /// + /// 节假日实体。 + /// DTO。 + public static StoreHolidayDto ToDto(StoreHoliday holiday) => new() + { + Id = holiday.Id, + TenantId = holiday.TenantId, + StoreId = holiday.StoreId, + Date = holiday.Date, + IsClosed = holiday.IsClosed, + Reason = holiday.Reason, + CreatedAt = holiday.CreatedAt + }; +} diff --git a/src/Application/TakeoutSaaS.Application/App/Stores/Validators/CreateStoreBusinessHourCommandValidator.cs b/src/Application/TakeoutSaaS.Application/App/Stores/Validators/CreateStoreBusinessHourCommandValidator.cs new file mode 100644 index 0000000..b0f1b80 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Stores/Validators/CreateStoreBusinessHourCommandValidator.cs @@ -0,0 +1,21 @@ +using FluentValidation; +using TakeoutSaaS.Application.App.Stores.Commands; + +namespace TakeoutSaaS.Application.App.Stores.Validators; + +/// +/// 创建营业时段命令验证器。 +/// +public sealed class CreateStoreBusinessHourCommandValidator : AbstractValidator +{ + /// + /// 初始化验证规则。 + /// + public CreateStoreBusinessHourCommandValidator() + { + RuleFor(x => x.StoreId).GreaterThan(0); + RuleFor(x => x.StartTime).LessThan(x => x.EndTime).WithMessage("结束时间必须晚于开始时间"); + RuleFor(x => x.CapacityLimit).GreaterThanOrEqualTo(0).When(x => x.CapacityLimit.HasValue); + RuleFor(x => x.Notes).MaximumLength(256); + } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Stores/Validators/CreateStoreDeliveryZoneCommandValidator.cs b/src/Application/TakeoutSaaS.Application/App/Stores/Validators/CreateStoreDeliveryZoneCommandValidator.cs new file mode 100644 index 0000000..b8878e3 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Stores/Validators/CreateStoreDeliveryZoneCommandValidator.cs @@ -0,0 +1,24 @@ +using FluentValidation; +using TakeoutSaaS.Application.App.Stores.Commands; + +namespace TakeoutSaaS.Application.App.Stores.Validators; + +/// +/// 创建配送区域命令验证器。 +/// +public sealed class CreateStoreDeliveryZoneCommandValidator : AbstractValidator +{ + /// + /// 初始化验证规则。 + /// + public CreateStoreDeliveryZoneCommandValidator() + { + RuleFor(x => x.StoreId).GreaterThan(0); + RuleFor(x => x.ZoneName).NotEmpty().MaximumLength(64); + RuleFor(x => x.PolygonGeoJson).NotEmpty(); + RuleFor(x => x.MinimumOrderAmount).GreaterThanOrEqualTo(0).When(x => x.MinimumOrderAmount.HasValue); + RuleFor(x => x.DeliveryFee).GreaterThanOrEqualTo(0).When(x => x.DeliveryFee.HasValue); + RuleFor(x => x.EstimatedMinutes).GreaterThan(0).When(x => x.EstimatedMinutes.HasValue); + RuleFor(x => x.SortOrder).GreaterThanOrEqualTo(0); + } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Stores/Validators/CreateStoreHolidayCommandValidator.cs b/src/Application/TakeoutSaaS.Application/App/Stores/Validators/CreateStoreHolidayCommandValidator.cs new file mode 100644 index 0000000..b6c140f --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Stores/Validators/CreateStoreHolidayCommandValidator.cs @@ -0,0 +1,20 @@ +using FluentValidation; +using TakeoutSaaS.Application.App.Stores.Commands; + +namespace TakeoutSaaS.Application.App.Stores.Validators; + +/// +/// 创建节假日命令验证器。 +/// +public sealed class CreateStoreHolidayCommandValidator : AbstractValidator +{ + /// + /// 初始化验证规则。 + /// + public CreateStoreHolidayCommandValidator() + { + RuleFor(x => x.StoreId).GreaterThan(0); + RuleFor(x => x.Date).NotEmpty(); + RuleFor(x => x.Reason).MaximumLength(256); + } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Stores/Validators/UpdateStoreBusinessHourCommandValidator.cs b/src/Application/TakeoutSaaS.Application/App/Stores/Validators/UpdateStoreBusinessHourCommandValidator.cs new file mode 100644 index 0000000..e7e76d5 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Stores/Validators/UpdateStoreBusinessHourCommandValidator.cs @@ -0,0 +1,22 @@ +using FluentValidation; +using TakeoutSaaS.Application.App.Stores.Commands; + +namespace TakeoutSaaS.Application.App.Stores.Validators; + +/// +/// 更新营业时段命令验证器。 +/// +public sealed class UpdateStoreBusinessHourCommandValidator : AbstractValidator +{ + /// + /// 初始化验证规则。 + /// + public UpdateStoreBusinessHourCommandValidator() + { + RuleFor(x => x.BusinessHourId).GreaterThan(0); + RuleFor(x => x.StoreId).GreaterThan(0); + RuleFor(x => x.StartTime).LessThan(x => x.EndTime).WithMessage("结束时间必须晚于开始时间"); + RuleFor(x => x.CapacityLimit).GreaterThanOrEqualTo(0).When(x => x.CapacityLimit.HasValue); + RuleFor(x => x.Notes).MaximumLength(256); + } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Stores/Validators/UpdateStoreDeliveryZoneCommandValidator.cs b/src/Application/TakeoutSaaS.Application/App/Stores/Validators/UpdateStoreDeliveryZoneCommandValidator.cs new file mode 100644 index 0000000..5de6927 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Stores/Validators/UpdateStoreDeliveryZoneCommandValidator.cs @@ -0,0 +1,25 @@ +using FluentValidation; +using TakeoutSaaS.Application.App.Stores.Commands; + +namespace TakeoutSaaS.Application.App.Stores.Validators; + +/// +/// 更新配送区域命令验证器。 +/// +public sealed class UpdateStoreDeliveryZoneCommandValidator : AbstractValidator +{ + /// + /// 初始化验证规则。 + /// + public UpdateStoreDeliveryZoneCommandValidator() + { + RuleFor(x => x.DeliveryZoneId).GreaterThan(0); + RuleFor(x => x.StoreId).GreaterThan(0); + RuleFor(x => x.ZoneName).NotEmpty().MaximumLength(64); + RuleFor(x => x.PolygonGeoJson).NotEmpty(); + RuleFor(x => x.MinimumOrderAmount).GreaterThanOrEqualTo(0).When(x => x.MinimumOrderAmount.HasValue); + RuleFor(x => x.DeliveryFee).GreaterThanOrEqualTo(0).When(x => x.DeliveryFee.HasValue); + RuleFor(x => x.EstimatedMinutes).GreaterThan(0).When(x => x.EstimatedMinutes.HasValue); + RuleFor(x => x.SortOrder).GreaterThanOrEqualTo(0); + } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Stores/Validators/UpdateStoreHolidayCommandValidator.cs b/src/Application/TakeoutSaaS.Application/App/Stores/Validators/UpdateStoreHolidayCommandValidator.cs new file mode 100644 index 0000000..4567bef --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Stores/Validators/UpdateStoreHolidayCommandValidator.cs @@ -0,0 +1,21 @@ +using FluentValidation; +using TakeoutSaaS.Application.App.Stores.Commands; + +namespace TakeoutSaaS.Application.App.Stores.Validators; + +/// +/// 更新节假日命令验证器。 +/// +public sealed class UpdateStoreHolidayCommandValidator : AbstractValidator +{ + /// + /// 初始化验证规则。 + /// + public UpdateStoreHolidayCommandValidator() + { + RuleFor(x => x.HolidayId).GreaterThan(0); + RuleFor(x => x.StoreId).GreaterThan(0); + RuleFor(x => x.Date).NotEmpty(); + RuleFor(x => x.Reason).MaximumLength(256); + } +} diff --git a/src/Domain/TakeoutSaaS.Domain/Stores/Repositories/IStoreRepository.cs b/src/Domain/TakeoutSaaS.Domain/Stores/Repositories/IStoreRepository.cs index bbbf7e0..252140b 100644 --- a/src/Domain/TakeoutSaaS.Domain/Stores/Repositories/IStoreRepository.cs +++ b/src/Domain/TakeoutSaaS.Domain/Stores/Repositories/IStoreRepository.cs @@ -26,16 +26,31 @@ public interface IStoreRepository /// Task> GetBusinessHoursAsync(long storeId, long tenantId, CancellationToken cancellationToken = default); + /// + /// 依据标识获取营业时段。 + /// + Task FindBusinessHourByIdAsync(long businessHourId, long tenantId, CancellationToken cancellationToken = default); + /// /// 获取门店配送区域配置。 /// Task> GetDeliveryZonesAsync(long storeId, long tenantId, CancellationToken cancellationToken = default); + /// + /// 依据标识获取配送区域。 + /// + Task FindDeliveryZoneByIdAsync(long deliveryZoneId, long tenantId, CancellationToken cancellationToken = default); + /// /// 获取门店节假日配置。 /// Task> GetHolidaysAsync(long storeId, long tenantId, CancellationToken cancellationToken = default); + /// + /// 依据标识获取节假日配置。 + /// + Task FindHolidayByIdAsync(long holidayId, long tenantId, CancellationToken cancellationToken = default); + /// /// 获取门店桌台区域。 /// @@ -61,16 +76,31 @@ public interface IStoreRepository /// Task AddBusinessHoursAsync(IEnumerable hours, CancellationToken cancellationToken = default); + /// + /// 更新营业时段。 + /// + Task UpdateBusinessHourAsync(StoreBusinessHour hour, CancellationToken cancellationToken = default); + /// /// 新增配送区域。 /// Task AddDeliveryZonesAsync(IEnumerable zones, CancellationToken cancellationToken = default); + /// + /// 更新配送区域。 + /// + Task UpdateDeliveryZoneAsync(StoreDeliveryZone zone, CancellationToken cancellationToken = default); + /// /// 新增节假日配置。 /// Task AddHolidaysAsync(IEnumerable holidays, CancellationToken cancellationToken = default); + /// + /// 更新节假日配置。 + /// + Task UpdateHolidayAsync(StoreHoliday holiday, CancellationToken cancellationToken = default); + /// /// 新增桌台区域。 /// @@ -91,6 +121,21 @@ public interface IStoreRepository /// Task SaveChangesAsync(CancellationToken cancellationToken = default); + /// + /// 删除营业时段。 + /// + Task DeleteBusinessHourAsync(long businessHourId, long tenantId, CancellationToken cancellationToken = default); + + /// + /// 删除配送区域。 + /// + Task DeleteDeliveryZoneAsync(long deliveryZoneId, long tenantId, CancellationToken cancellationToken = default); + + /// + /// 删除节假日。 + /// + Task DeleteHolidayAsync(long holidayId, long tenantId, CancellationToken cancellationToken = default); + /// /// 更新门店。 /// diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfStoreRepository.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfStoreRepository.cs index 3a0934a..b944cf3 100644 --- a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfStoreRepository.cs +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfStoreRepository.cs @@ -58,6 +58,14 @@ public sealed class EfStoreRepository(TakeoutAppDbContext context) : IStoreRepos return hours; } + /// + public Task FindBusinessHourByIdAsync(long businessHourId, long tenantId, CancellationToken cancellationToken = default) + { + return context.StoreBusinessHours + .Where(x => x.TenantId == tenantId && x.Id == businessHourId) + .FirstOrDefaultAsync(cancellationToken); + } + /// public async Task> GetDeliveryZonesAsync(long storeId, long tenantId, CancellationToken cancellationToken = default) { @@ -70,6 +78,14 @@ public sealed class EfStoreRepository(TakeoutAppDbContext context) : IStoreRepos return zones; } + /// + public Task FindDeliveryZoneByIdAsync(long deliveryZoneId, long tenantId, CancellationToken cancellationToken = default) + { + return context.StoreDeliveryZones + .Where(x => x.TenantId == tenantId && x.Id == deliveryZoneId) + .FirstOrDefaultAsync(cancellationToken); + } + /// public async Task> GetHolidaysAsync(long storeId, long tenantId, CancellationToken cancellationToken = default) { @@ -82,6 +98,14 @@ public sealed class EfStoreRepository(TakeoutAppDbContext context) : IStoreRepos return holidays; } + /// + public Task FindHolidayByIdAsync(long holidayId, long tenantId, CancellationToken cancellationToken = default) + { + return context.StoreHolidays + .Where(x => x.TenantId == tenantId && x.Id == holidayId) + .FirstOrDefaultAsync(cancellationToken); + } + /// public async Task> GetTableAreasAsync(long storeId, long tenantId, CancellationToken cancellationToken = default) { @@ -131,18 +155,39 @@ public sealed class EfStoreRepository(TakeoutAppDbContext context) : IStoreRepos return context.StoreBusinessHours.AddRangeAsync(hours, cancellationToken); } + /// + public Task UpdateBusinessHourAsync(StoreBusinessHour hour, CancellationToken cancellationToken = default) + { + context.StoreBusinessHours.Update(hour); + return Task.CompletedTask; + } + /// public Task AddDeliveryZonesAsync(IEnumerable zones, CancellationToken cancellationToken = default) { return context.StoreDeliveryZones.AddRangeAsync(zones, cancellationToken); } + /// + public Task UpdateDeliveryZoneAsync(StoreDeliveryZone zone, CancellationToken cancellationToken = default) + { + context.StoreDeliveryZones.Update(zone); + return Task.CompletedTask; + } + /// public Task AddHolidaysAsync(IEnumerable holidays, CancellationToken cancellationToken = default) { return context.StoreHolidays.AddRangeAsync(holidays, cancellationToken); } + /// + public Task UpdateHolidayAsync(StoreHoliday holiday, CancellationToken cancellationToken = default) + { + context.StoreHolidays.Update(holiday); + return Task.CompletedTask; + } + /// public Task AddTableAreasAsync(IEnumerable areas, CancellationToken cancellationToken = default) { @@ -167,6 +212,45 @@ public sealed class EfStoreRepository(TakeoutAppDbContext context) : IStoreRepos return context.SaveChangesAsync(cancellationToken); } + /// + public async Task DeleteBusinessHourAsync(long businessHourId, long tenantId, CancellationToken cancellationToken = default) + { + var existing = await context.StoreBusinessHours + .Where(x => x.TenantId == tenantId && x.Id == businessHourId) + .FirstOrDefaultAsync(cancellationToken); + + if (existing != null) + { + context.StoreBusinessHours.Remove(existing); + } + } + + /// + public async Task DeleteDeliveryZoneAsync(long deliveryZoneId, long tenantId, CancellationToken cancellationToken = default) + { + var existing = await context.StoreDeliveryZones + .Where(x => x.TenantId == tenantId && x.Id == deliveryZoneId) + .FirstOrDefaultAsync(cancellationToken); + + if (existing != null) + { + context.StoreDeliveryZones.Remove(existing); + } + } + + /// + public async Task DeleteHolidayAsync(long holidayId, long tenantId, CancellationToken cancellationToken = default) + { + var existing = await context.StoreHolidays + .Where(x => x.TenantId == tenantId && x.Id == holidayId) + .FirstOrDefaultAsync(cancellationToken); + + if (existing != null) + { + context.StoreHolidays.Remove(existing); + } + } + /// public Task UpdateStoreAsync(Store store, CancellationToken cancellationToken = default) {