feat(api): .NET 10 multi-tenant REST API
Full backend implementation: - Multi-tenant cafe/restaurant management (menus, orders, tables, staff) - POS order flow with ZarinPal and Snappfood payment integration - OTP authentication via Kavenegar SMS - QR digital menu with public discover/finder endpoints - Customer loyalty, coupons, CRM - PostgreSQL via EF Core, Redis for caching/sessions - Background jobs, webhook handlers - Full migration history Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
using FluentValidation;
|
||||
using Meezi.API.Models.Menu;
|
||||
using Meezi.API.Models.Orders;
|
||||
using Meezi.API.Models.Tables;
|
||||
using Meezi.Core.Constants;
|
||||
using Meezi.Core.Utilities;
|
||||
|
||||
namespace Meezi.API.Validators;
|
||||
|
||||
public class CreateMenuCategoryRequestValidator : AbstractValidator<CreateMenuCategoryRequest>
|
||||
{
|
||||
public CreateMenuCategoryRequestValidator()
|
||||
{
|
||||
RuleFor(x => x.Name).NotEmpty().MaximumLength(200);
|
||||
RuleFor(x => x.NameEn).NotEmpty().MaximumLength(200);
|
||||
RuleFor(x => x.SortOrder).GreaterThanOrEqualTo(0);
|
||||
RuleFor(x => x.DiscountPercent).InclusiveBetween(0, 100);
|
||||
RuleFor(x => x.Icon).MaximumLength(32).When(x => x.Icon is not null);
|
||||
RuleFor(x => x.IconPresetId)
|
||||
.Must(id => id is null || CategoryIconPresets.IsValidPreset(id))
|
||||
.WithMessage("Invalid icon preset.")
|
||||
.MaximumLength(48)
|
||||
.When(x => x.IconPresetId is not null);
|
||||
RuleFor(x => x.IconStyle)
|
||||
.Must(s => s is null || CategoryIconPresets.IsValidStyle(s))
|
||||
.WithMessage("Invalid icon style.")
|
||||
.MaximumLength(16)
|
||||
.When(x => x.IconStyle is not null);
|
||||
RuleFor(x => x.ImageUrl).MaximumLength(500).When(x => x.ImageUrl is not null);
|
||||
RuleFor(x => x)
|
||||
.Must(x => string.IsNullOrWhiteSpace(x.IconPresetId) == string.IsNullOrWhiteSpace(x.IconStyle))
|
||||
.WithMessage("Icon preset and style must be set together.");
|
||||
}
|
||||
}
|
||||
|
||||
public class CreateMenuItemRequestValidator : AbstractValidator<CreateMenuItemRequest>
|
||||
{
|
||||
public CreateMenuItemRequestValidator()
|
||||
{
|
||||
RuleFor(x => x.CategoryId).NotEmpty();
|
||||
RuleFor(x => x.Name).NotEmpty().MaximumLength(200);
|
||||
RuleFor(x => x.NameEn).NotEmpty().MaximumLength(200);
|
||||
RuleFor(x => x.Price).GreaterThanOrEqualTo(0);
|
||||
RuleFor(x => x.DiscountPercent).InclusiveBetween(0, 100);
|
||||
}
|
||||
}
|
||||
|
||||
public class CreateTableRequestValidator : AbstractValidator<CreateTableRequest>
|
||||
{
|
||||
public CreateTableRequestValidator()
|
||||
{
|
||||
RuleFor(x => x.Number).NotEmpty().MaximumLength(50);
|
||||
RuleFor(x => x.Capacity).GreaterThan(0);
|
||||
}
|
||||
}
|
||||
|
||||
public class PatchTableRequestValidator : AbstractValidator<PatchTableRequest>
|
||||
{
|
||||
public PatchTableRequestValidator()
|
||||
{
|
||||
RuleFor(x => x.Number).NotEmpty().MaximumLength(50).When(x => x.Number is not null);
|
||||
RuleFor(x => x.Capacity).GreaterThan(0).When(x => x.Capacity.HasValue);
|
||||
}
|
||||
}
|
||||
|
||||
public class CreateBranchTableRequestValidator : AbstractValidator<CreateBranchTableRequest>
|
||||
{
|
||||
public CreateBranchTableRequestValidator()
|
||||
{
|
||||
RuleFor(x => x.Number).NotEmpty().MaximumLength(50);
|
||||
RuleFor(x => x.Capacity).GreaterThan(0);
|
||||
}
|
||||
}
|
||||
|
||||
public class PatchBranchTableRequestValidator : AbstractValidator<PatchBranchTableRequest>
|
||||
{
|
||||
public PatchBranchTableRequestValidator()
|
||||
{
|
||||
RuleFor(x => x.Number).NotEmpty().MaximumLength(50).When(x => x.Number is not null);
|
||||
RuleFor(x => x.Capacity).GreaterThan(0).When(x => x.Capacity.HasValue);
|
||||
}
|
||||
}
|
||||
|
||||
public class CreateTableSectionRequestValidator : AbstractValidator<CreateTableSectionRequest>
|
||||
{
|
||||
public CreateTableSectionRequestValidator()
|
||||
{
|
||||
RuleFor(x => x.Name).NotEmpty().MaximumLength(100);
|
||||
}
|
||||
}
|
||||
|
||||
public class PatchTableSectionRequestValidator : AbstractValidator<PatchTableSectionRequest>
|
||||
{
|
||||
public PatchTableSectionRequestValidator()
|
||||
{
|
||||
RuleFor(x => x.Name).NotEmpty().MaximumLength(100).When(x => x.Name is not null);
|
||||
}
|
||||
}
|
||||
|
||||
public class CreateOrderRequestValidator : AbstractValidator<CreateOrderRequest>
|
||||
{
|
||||
public CreateOrderRequestValidator()
|
||||
{
|
||||
RuleFor(x => x.OrderType).IsInEnum();
|
||||
RuleFor(x => x.Items).NotEmpty();
|
||||
RuleFor(x => x.GuestName).MaximumLength(200).When(x => x.GuestName is not null);
|
||||
RuleFor(x => x.GuestPhone)
|
||||
.Must(p => PhoneNormalizer.IsValidIranMobile(PhoneNormalizer.Normalize(p!)))
|
||||
.When(x => !string.IsNullOrWhiteSpace(x.GuestPhone));
|
||||
RuleForEach(x => x.Items).ChildRules(item =>
|
||||
{
|
||||
item.RuleFor(i => i.MenuItemId).NotEmpty();
|
||||
item.RuleFor(i => i.Quantity).GreaterThan(0);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public class UpdateOrderStatusRequestValidator : AbstractValidator<UpdateOrderStatusRequest>
|
||||
{
|
||||
public UpdateOrderStatusRequestValidator()
|
||||
{
|
||||
RuleFor(x => x.Status).IsInEnum();
|
||||
}
|
||||
}
|
||||
|
||||
public class RecordPaymentsRequestValidator : AbstractValidator<RecordPaymentsRequest>
|
||||
{
|
||||
public RecordPaymentsRequestValidator()
|
||||
{
|
||||
RuleFor(x => x.Payments).NotEmpty();
|
||||
RuleForEach(x => x.Payments).ChildRules(p =>
|
||||
{
|
||||
p.RuleFor(x => x.Method).IsInEnum();
|
||||
p.RuleFor(x => x.Amount).GreaterThan(0);
|
||||
});
|
||||
RuleFor(x => x.LoyaltyPointsToRedeem)
|
||||
.GreaterThanOrEqualTo(0)
|
||||
.When(x => x.LoyaltyPointsToRedeem.HasValue);
|
||||
}
|
||||
}
|
||||
|
||||
public class AppendOrderItemsRequestValidator : AbstractValidator<AppendOrderItemsRequest>
|
||||
{
|
||||
public AppendOrderItemsRequestValidator()
|
||||
{
|
||||
RuleFor(x => x.Items).NotEmpty();
|
||||
RuleForEach(x => x.Items).ChildRules(item =>
|
||||
{
|
||||
item.RuleFor(i => i.MenuItemId).NotEmpty();
|
||||
item.RuleFor(i => i.Quantity).GreaterThan(0);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public class UpdateOrderSessionRequestValidator : AbstractValidator<UpdateOrderSessionRequest>
|
||||
{
|
||||
public UpdateOrderSessionRequestValidator()
|
||||
{
|
||||
RuleFor(x => x.GuestName).MaximumLength(200).When(x => x.GuestName is not null);
|
||||
RuleFor(x => x.GuestPhone)
|
||||
.Must(p => PhoneNormalizer.IsValidIranMobile(PhoneNormalizer.Normalize(p!)))
|
||||
.When(x => !string.IsNullOrWhiteSpace(x.GuestPhone));
|
||||
}
|
||||
}
|
||||
|
||||
public class SetTableCleaningRequestValidator : AbstractValidator<SetTableCleaningRequest>
|
||||
{
|
||||
public SetTableCleaningRequestValidator() { }
|
||||
}
|
||||
Reference in New Issue
Block a user