完成门店管理后端接口与任务

This commit is contained in:
2026-01-01 07:26:14 +08:00
parent dc9f6136d6
commit fc55003d3d
131 changed files with 15333 additions and 201 deletions

View File

@@ -0,0 +1,40 @@
using FluentValidation;
using TakeoutSaaS.Application.App.Stores.Commands;
namespace TakeoutSaaS.Application.App.Stores.Validators;
/// <summary>
/// 批量更新营业时段命令验证器。
/// </summary>
public sealed class BatchUpdateBusinessHoursCommandValidator : AbstractValidator<BatchUpdateBusinessHoursCommand>
{
/// <summary>
/// 初始化验证规则。
/// </summary>
public BatchUpdateBusinessHoursCommandValidator()
{
RuleFor(x => x.StoreId).GreaterThan(0);
RuleForEach(x => x.Items).ChildRules(item =>
{
item.RuleFor(x => x.StartTime).NotNull();
item.RuleFor(x => x.EndTime).NotNull();
item.RuleFor(x => x.CapacityLimit).GreaterThanOrEqualTo(0).When(x => x.CapacityLimit.HasValue);
item.RuleFor(x => x.Notes).MaximumLength(256);
});
RuleFor(x => x.Items).Custom((items, context) =>
{
if (items == null || items.Count == 0)
{
return;
}
var error = BusinessHourValidators.ValidateOverlap(items);
if (!string.IsNullOrWhiteSpace(error))
{
context.AddFailure(error);
}
});
}
}

View File

@@ -0,0 +1,65 @@
using System.Linq;
using TakeoutSaaS.Application.App.Stores.Dto;
namespace TakeoutSaaS.Application.App.Stores.Validators;
/// <summary>
/// 营业时段校验助手。
/// </summary>
public static class BusinessHourValidators
{
/// <summary>
/// 校验营业时段是否存在重叠。
/// </summary>
/// <param name="items">营业时段列表。</param>
/// <returns>错误信息,若为空表示通过。</returns>
public static string? ValidateOverlap(IReadOnlyList<StoreBusinessHourInputDto> items)
{
if (items.Count == 0)
{
return null;
}
var segments = new List<(DayOfWeek Day, TimeSpan Start, TimeSpan End)>();
foreach (var item in items)
{
if (item.StartTime == item.EndTime)
{
return "营业时段开始时间不能等于结束时间";
}
if (item.StartTime < item.EndTime)
{
segments.Add((item.DayOfWeek, item.StartTime, item.EndTime));
continue;
}
var nextDay = NextDay(item.DayOfWeek);
segments.Add((item.DayOfWeek, item.StartTime, TimeSpan.FromDays(1)));
segments.Add((nextDay, TimeSpan.Zero, item.EndTime));
}
var grouped = segments.GroupBy(x => x.Day).ToList();
foreach (var group in grouped)
{
var ordered = group.OrderBy(x => x.Start).ToList();
for (var index = 0; index < ordered.Count - 1; index++)
{
var current = ordered[index];
var next = ordered[index + 1];
if (next.Start < current.End)
{
return "营业时段存在重叠,请调整";
}
}
}
return null;
}
private static DayOfWeek NextDay(DayOfWeek day)
{
var next = (int)day + 1;
return next > 6 ? DayOfWeek.Sunday : (DayOfWeek)next;
}
}

View File

@@ -0,0 +1,26 @@
using FluentValidation;
using TakeoutSaaS.Application.App.Stores.Queries;
namespace TakeoutSaaS.Application.App.Stores.Validators;
/// <summary>
/// 费用预览计算查询验证器。
/// </summary>
public sealed class CalculateStoreFeeQueryValidator : AbstractValidator<CalculateStoreFeeQuery>
{
/// <summary>
/// 初始化验证规则。
/// </summary>
public CalculateStoreFeeQueryValidator()
{
RuleFor(x => x.StoreId).GreaterThan(0);
RuleFor(x => x.OrderAmount).GreaterThanOrEqualTo(0);
RuleForEach(x => x.Items).ChildRules(item =>
{
item.RuleFor(x => x.SkuId).GreaterThan(0);
item.RuleFor(x => x.Quantity).GreaterThan(0);
item.RuleFor(x => x.PackagingFee).GreaterThanOrEqualTo(0);
});
}
}

View File

@@ -0,0 +1,20 @@
using FluentValidation;
using TakeoutSaaS.Application.App.Stores.Queries;
namespace TakeoutSaaS.Application.App.Stores.Validators;
/// <summary>
/// 配送范围检测查询验证器。
/// </summary>
public sealed class CheckStoreDeliveryZoneQueryValidator : AbstractValidator<CheckStoreDeliveryZoneQuery>
{
/// <summary>
/// 初始化验证规则。
/// </summary>
public CheckStoreDeliveryZoneQueryValidator()
{
RuleFor(x => x.StoreId).GreaterThan(0);
RuleFor(x => x.Longitude).InclusiveBetween(-180, 180);
RuleFor(x => x.Latitude).InclusiveBetween(-90, 90);
}
}

View File

@@ -14,7 +14,7 @@ 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.StartTime).NotEqual(x => x.EndTime).WithMessage("开始时间不能等于结束时间");
RuleFor(x => x.CapacityLimit).GreaterThanOrEqualTo(0).When(x => x.CapacityLimit.HasValue);
RuleFor(x => x.Notes).MaximumLength(256);
}

View File

@@ -18,10 +18,14 @@ public sealed class CreateStoreCommandValidator : AbstractValidator<CreateStoreC
RuleFor(x => x.Name).NotEmpty().MaximumLength(128);
RuleFor(x => x.Phone).MaximumLength(32);
RuleFor(x => x.ManagerName).MaximumLength(64);
RuleFor(x => x.SignboardImageUrl).NotEmpty().MaximumLength(500);
RuleFor(x => x.OwnershipType).IsInEnum();
RuleFor(x => x.Province).MaximumLength(64);
RuleFor(x => x.City).MaximumLength(64);
RuleFor(x => x.District).MaximumLength(64);
RuleFor(x => x.Address).MaximumLength(256);
RuleFor(x => x.Longitude).NotNull();
RuleFor(x => x.Latitude).NotNull();
RuleFor(x => x.Announcement).MaximumLength(512);
RuleFor(x => x.Tags).MaximumLength(256);
RuleFor(x => x.DeliveryRadiusKm).GreaterThanOrEqualTo(0);

View File

@@ -0,0 +1,42 @@
using FluentValidation;
using TakeoutSaaS.Application.App.Stores.Commands;
using TakeoutSaaS.Domain.Stores.Enums;
namespace TakeoutSaaS.Application.App.Stores.Validators;
/// <summary>
/// 创建门店资质命令验证器。
/// </summary>
public sealed class CreateStoreQualificationCommandValidator : AbstractValidator<CreateStoreQualificationCommand>
{
/// <summary>
/// 初始化验证规则。
/// </summary>
public CreateStoreQualificationCommandValidator()
{
RuleFor(x => x.StoreId).GreaterThan(0);
RuleFor(x => x.QualificationType).IsInEnum();
RuleFor(x => x.FileUrl).NotEmpty().MaximumLength(500);
RuleFor(x => x.SortOrder).GreaterThanOrEqualTo(0);
RuleFor(x => x.DocumentNumber).MaximumLength(100);
RuleFor(x => x.ExpiresAt)
.Must(date => date.HasValue && date.Value.Date > DateTime.UtcNow.Date)
.When(x => IsLicenseType(x.QualificationType))
.WithMessage("证照有效期必须晚于今天");
RuleFor(x => x.DocumentNumber)
.NotEmpty()
.MinimumLength(2)
.When(x => IsLicenseType(x.QualificationType))
.WithMessage("证照编号不能为空");
RuleFor(x => x.ExpiresAt)
.Must(date => !date.HasValue || date.Value.Date > DateTime.UtcNow.Date)
.When(x => !IsLicenseType(x.QualificationType))
.WithMessage("证照有效期必须晚于今天");
}
private static bool IsLicenseType(StoreQualificationType type)
=> type is StoreQualificationType.BusinessLicense or StoreQualificationType.FoodServiceLicense;
}

View File

@@ -0,0 +1,19 @@
using FluentValidation;
using TakeoutSaaS.Application.App.Stores.Commands;
namespace TakeoutSaaS.Application.App.Stores.Validators;
/// <summary>
/// 删除门店资质命令验证器。
/// </summary>
public sealed class DeleteStoreQualificationCommandValidator : AbstractValidator<DeleteStoreQualificationCommand>
{
/// <summary>
/// 初始化验证规则。
/// </summary>
public DeleteStoreQualificationCommandValidator()
{
RuleFor(x => x.StoreId).GreaterThan(0);
RuleFor(x => x.QualificationId).GreaterThan(0);
}
}

View File

@@ -0,0 +1,18 @@
using FluentValidation;
using TakeoutSaaS.Application.App.Stores.Commands;
namespace TakeoutSaaS.Application.App.Stores.Validators;
/// <summary>
/// 提交门店审核命令验证器。
/// </summary>
public sealed class SubmitStoreAuditCommandValidator : AbstractValidator<SubmitStoreAuditCommand>
{
/// <summary>
/// 初始化验证规则。
/// </summary>
public SubmitStoreAuditCommandValidator()
{
RuleFor(x => x.StoreId).GreaterThan(0);
}
}

View File

@@ -0,0 +1,29 @@
using FluentValidation;
using TakeoutSaaS.Application.App.Stores.Commands;
using TakeoutSaaS.Domain.Stores.Enums;
namespace TakeoutSaaS.Application.App.Stores.Validators;
/// <summary>
/// 切换门店经营状态命令验证器。
/// </summary>
public sealed class ToggleBusinessStatusCommandValidator : AbstractValidator<ToggleBusinessStatusCommand>
{
/// <summary>
/// 初始化验证规则。
/// </summary>
public ToggleBusinessStatusCommandValidator()
{
RuleFor(x => x.StoreId).GreaterThan(0);
RuleFor(x => x.BusinessStatus)
.Must(status => status is StoreBusinessStatus.Open or StoreBusinessStatus.Resting)
.WithMessage("仅允许切换营业中或休息中");
RuleFor(x => x.ClosureReason)
.NotNull()
.When(x => x.BusinessStatus == StoreBusinessStatus.Resting)
.WithMessage("切换休息中必须选择歇业原因");
RuleFor(x => x.ClosureReasonText).MaximumLength(500);
}
}

View File

@@ -15,7 +15,7 @@ public sealed class UpdateStoreBusinessHourCommandValidator : AbstractValidator<
{
RuleFor(x => x.BusinessHourId).GreaterThan(0);
RuleFor(x => x.StoreId).GreaterThan(0);
RuleFor(x => x.StartTime).LessThan(x => x.EndTime).WithMessage("结束时间必须晚于开始时间");
RuleFor(x => x.StartTime).NotEqual(x => x.EndTime).WithMessage("开始时间不能等于结束时间");
RuleFor(x => x.CapacityLimit).GreaterThanOrEqualTo(0).When(x => x.CapacityLimit.HasValue);
RuleFor(x => x.Notes).MaximumLength(256);
}

View File

@@ -19,6 +19,7 @@ public sealed class UpdateStoreCommandValidator : AbstractValidator<UpdateStoreC
RuleFor(x => x.Name).NotEmpty().MaximumLength(128);
RuleFor(x => x.Phone).MaximumLength(32);
RuleFor(x => x.ManagerName).MaximumLength(64);
RuleFor(x => x.SignboardImageUrl).MaximumLength(500);
RuleFor(x => x.Province).MaximumLength(64);
RuleFor(x => x.City).MaximumLength(64);
RuleFor(x => x.District).MaximumLength(64);

View File

@@ -0,0 +1,35 @@
using FluentValidation;
using TakeoutSaaS.Application.App.Stores.Commands;
using TakeoutSaaS.Domain.Stores.Enums;
namespace TakeoutSaaS.Application.App.Stores.Validators;
/// <summary>
/// 更新门店费用配置命令验证器。
/// </summary>
public sealed class UpdateStoreFeeCommandValidator : AbstractValidator<UpdateStoreFeeCommand>
{
/// <summary>
/// 初始化验证规则。
/// </summary>
public UpdateStoreFeeCommandValidator()
{
RuleFor(x => x.StoreId).GreaterThan(0);
RuleFor(x => x.MinimumOrderAmount).GreaterThanOrEqualTo(0).LessThanOrEqualTo(9999.99m);
RuleFor(x => x.DeliveryFee).GreaterThanOrEqualTo(0).LessThanOrEqualTo(999.99m);
RuleFor(x => x.FreeDeliveryThreshold).GreaterThanOrEqualTo(0).When(x => x.FreeDeliveryThreshold.HasValue);
RuleFor(x => x.FixedPackagingFee)
.NotNull()
.When(x => x.PackagingFeeMode == PackagingFeeMode.Fixed)
.WithMessage("总计打包费模式下必须填写固定打包费");
RuleFor(x => x.FixedPackagingFee)
.Must(fee => !fee.HasValue || fee.Value <= 99.99m)
.WithMessage("固定打包费不能超过 99.99");
RuleFor(x => x.FixedPackagingFee)
.Must(fee => !fee.HasValue || fee.Value >= 0)
.WithMessage("固定打包费不能为负数");
}
}

View File

@@ -0,0 +1,26 @@
using FluentValidation;
using TakeoutSaaS.Application.App.Stores.Commands;
namespace TakeoutSaaS.Application.App.Stores.Validators;
/// <summary>
/// 更新门店资质命令验证器。
/// </summary>
public sealed class UpdateStoreQualificationCommandValidator : AbstractValidator<UpdateStoreQualificationCommand>
{
/// <summary>
/// 初始化验证规则。
/// </summary>
public UpdateStoreQualificationCommandValidator()
{
RuleFor(x => x.StoreId).GreaterThan(0);
RuleFor(x => x.QualificationId).GreaterThan(0);
RuleFor(x => x.FileUrl).MaximumLength(500).When(x => !string.IsNullOrWhiteSpace(x.FileUrl));
RuleFor(x => x.DocumentNumber).MaximumLength(100).When(x => !string.IsNullOrWhiteSpace(x.DocumentNumber));
RuleFor(x => x.SortOrder).GreaterThanOrEqualTo(0).When(x => x.SortOrder.HasValue);
RuleFor(x => x.ExpiresAt)
.Must(date => !date.HasValue || date.Value.Date > DateTime.UtcNow.Date)
.When(x => x.ExpiresAt.HasValue)
.WithMessage("证照有效期必须晚于今天");
}
}