024a455ab3
Dashboard & API bug fixes for owner-reported breakage: - MenuController validators (PosValidators): NameEn was required but the dashboard sends null when blank, so every manual menu-item create failed and category create failed 100% (the form never sends nameEn). Now optional. - DemoDataBanner: only showed when a cafe was exactly empty, so showcase-seeded cafes (2-3 cats / 3-5 items) could never trigger the one-click seed. Widened gate to sparse menus (<5 cats && <10 items) and added a clear "nothing to add" message when already populated. - client.ts: added one-time JWT refresh-and-retry on 401 (shared in-flight promise) before bouncing to /login. Expired access tokens silently broke ticket list, add-table, and other reads. - Surface API errors as toasts on menu + table mutations (were swallowed silently, so failures looked like "nothing happens"). - Admin blog editor: saving an edit dropped IsPublished (defaulted false, silently unpublishing the post on every save); now persisted with a toggle. Also hoisted the inner Field component to module scope - it was remounting every input on each keystroke and dropping focus. - Admin integrations: replaced raw radio gateway selector with a styled RadioDot matching the iOS toggles. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
170 lines
5.8 KiB
C#
170 lines
5.8 KiB
C#
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).MaximumLength(200).When(x => !string.IsNullOrWhiteSpace(x.NameEn));
|
|
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).MaximumLength(200).When(x => !string.IsNullOrWhiteSpace(x.NameEn));
|
|
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() { }
|
|
}
|