mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-09-30 04:53:11 +00:00
* feat(vendors): add company roster management * fix(security): remove request-controlled write guards * fix(vendors): synchronize roster company fields * fix(vendors): source facets from companies
194 lines
8.5 KiB
C#
194 lines
8.5 KiB
C#
using FluentValidation;
|
|
using SeaHaven.Services.DTOs;
|
|
using SeaHaven.Services.Helpers;
|
|
|
|
namespace SeaHaven.Services.Validation
|
|
{
|
|
public interface ICreateVendorValidation : IValidator<CreateVendorDTO>
|
|
{
|
|
}
|
|
|
|
public class CreateVendorValidation : AbstractValidator<CreateVendorDTO>, ICreateVendorValidation
|
|
{
|
|
public CreateVendorValidation()
|
|
{
|
|
RuleFor(x => x.Name)
|
|
.NotEmpty().WithMessage("Vendor name is required")
|
|
.MaximumLength(200).WithMessage("Vendor name cannot exceed 200 characters");
|
|
|
|
RuleFor(x => x.Email)
|
|
.EmailAddress().WithMessage("Invalid email address")
|
|
.When(x => !string.IsNullOrEmpty(x.Email));
|
|
|
|
// SH-199: technician (ContactName/Phone) is optional. The company must still
|
|
// expose at least one contact channel: a company phone or an email.
|
|
RuleFor(x => x)
|
|
.Must(dto => !string.IsNullOrWhiteSpace(dto.CompanyPhone) || !string.IsNullOrWhiteSpace(dto.Email))
|
|
.WithMessage("At least one company phone or email is required")
|
|
.OverridePropertyName(nameof(CreateVendorDTO.CompanyPhone));
|
|
|
|
RuleFor(x => x.ContactName)
|
|
.MaximumLength(200).WithMessage("Contact name cannot exceed 200 characters")
|
|
.When(x => !string.IsNullOrEmpty(x.ContactName));
|
|
|
|
RuleFor(x => x.PreferredContact)
|
|
.Must(VendorValidationRules.BeValidPreferredContact)
|
|
.WithMessage("PreferredContact must be one of: Phone, Email, Text")
|
|
.When(x => !string.IsNullOrEmpty(x.PreferredContact));
|
|
|
|
RuleFor(x => x.TradeSpecialties)
|
|
.MaximumLength(1000).WithMessage("Trade specialties cannot exceed 1000 characters")
|
|
.When(x => !string.IsNullOrEmpty(x.TradeSpecialties));
|
|
|
|
RuleFor(x => x.Phone)
|
|
.Must(VendorValidationRules.BeValidNorthAmericanPhone)
|
|
.WithMessage("Phone must be in North American format: (XXX) XXX-XXXX")
|
|
.When(x => !string.IsNullOrEmpty(x.Phone));
|
|
|
|
RuleFor(x => x.CompanyPhone)
|
|
.Must(VendorValidationRules.BeValidNorthAmericanPhone)
|
|
.WithMessage("Company phone must be in North American format: (XXX) XXX-XXXX")
|
|
.When(x => !string.IsNullOrEmpty(x.CompanyPhone));
|
|
|
|
RuleFor(x => x.Address)
|
|
.MaximumLength(500).WithMessage("Address cannot exceed 500 characters")
|
|
.When(x => !string.IsNullOrEmpty(x.Address));
|
|
|
|
RuleFor(x => x.City)
|
|
.MaximumLength(100).WithMessage("City cannot exceed 100 characters")
|
|
.When(x => !string.IsNullOrEmpty(x.City));
|
|
|
|
RuleFor(x => x.State)
|
|
.MaximumLength(2).WithMessage("State must be 2 characters")
|
|
.When(x => !string.IsNullOrEmpty(x.State));
|
|
|
|
RuleFor(x => x.Zipcode)
|
|
.MaximumLength(10).WithMessage("Zipcode cannot exceed 10 characters")
|
|
.When(x => !string.IsNullOrEmpty(x.Zipcode));
|
|
|
|
RuleFor(x => x.GoogleMapsUrl)
|
|
.Must(VendorValidationRules.BeValidHttpsUrl).WithMessage("Google Maps URL must be a valid HTTPS URL")
|
|
.When(x => !string.IsNullOrEmpty(x.GoogleMapsUrl));
|
|
|
|
RuleFor(x => x.Notes)
|
|
.MaximumLength(2000).WithMessage("Notes cannot exceed 2000 characters")
|
|
.When(x => !string.IsNullOrEmpty(x.Notes));
|
|
}
|
|
}
|
|
|
|
public interface IUpdateVendorValidation : IValidator<UpdateVendorDTO>
|
|
{
|
|
}
|
|
|
|
public class UpdateVendorValidation : AbstractValidator<UpdateVendorDTO>, IUpdateVendorValidation
|
|
{
|
|
public UpdateVendorValidation()
|
|
{
|
|
RuleFor(x => x.Name)
|
|
.MaximumLength(200).WithMessage("Vendor name cannot exceed 200 characters")
|
|
.When(x => !string.IsNullOrEmpty(x.Name));
|
|
|
|
RuleFor(x => x.Email)
|
|
.EmailAddress().WithMessage("Invalid email address")
|
|
.When(x => !string.IsNullOrEmpty(x.Email));
|
|
|
|
RuleFor(x => x.ContactName)
|
|
.NotEmpty().WithMessage("Technician name is required")
|
|
.MaximumLength(200).WithMessage("Contact name cannot exceed 200 characters")
|
|
.When(x => x.ContactName != null);
|
|
|
|
RuleFor(x => x.PreferredContact)
|
|
.Must(VendorValidationRules.BeValidPreferredContact)
|
|
.WithMessage("PreferredContact must be one of: Phone, Email, Text")
|
|
.When(x => !string.IsNullOrEmpty(x.PreferredContact));
|
|
|
|
RuleFor(x => x.TradeSpecialties)
|
|
.MaximumLength(1000).WithMessage("Trade specialties cannot exceed 1000 characters")
|
|
.When(x => !string.IsNullOrEmpty(x.TradeSpecialties));
|
|
|
|
RuleFor(x => x.Phone)
|
|
.Must(VendorValidationRules.BeValidNorthAmericanPhone)
|
|
.WithMessage("Phone must be in North American format: (XXX) XXX-XXXX")
|
|
.When(x => !string.IsNullOrEmpty(x.Phone));
|
|
|
|
RuleFor(x => x.CompanyPhone)
|
|
.Must(VendorValidationRules.BeValidNorthAmericanPhone)
|
|
.WithMessage("Company phone must be in North American format: (XXX) XXX-XXXX")
|
|
.When(x => !string.IsNullOrEmpty(x.CompanyPhone));
|
|
|
|
RuleFor(x => x.Address)
|
|
.MaximumLength(500).WithMessage("Address cannot exceed 500 characters")
|
|
.When(x => !string.IsNullOrEmpty(x.Address));
|
|
|
|
RuleFor(x => x.City)
|
|
.MaximumLength(100).WithMessage("City cannot exceed 100 characters")
|
|
.When(x => !string.IsNullOrEmpty(x.City));
|
|
|
|
RuleFor(x => x.State)
|
|
.MaximumLength(2).WithMessage("State must be 2 characters")
|
|
.When(x => !string.IsNullOrEmpty(x.State));
|
|
|
|
RuleFor(x => x.Zipcode)
|
|
.MaximumLength(10).WithMessage("Zipcode cannot exceed 10 characters")
|
|
.When(x => !string.IsNullOrEmpty(x.Zipcode));
|
|
|
|
RuleFor(x => x.GoogleMapsUrl)
|
|
.Must(VendorValidationRules.BeValidHttpsUrl).WithMessage("Google Maps URL must be a valid HTTPS URL")
|
|
.When(x => !string.IsNullOrEmpty(x.GoogleMapsUrl));
|
|
|
|
RuleFor(x => x.Notes)
|
|
.MaximumLength(2000).WithMessage("Notes cannot exceed 2000 characters")
|
|
.When(x => !string.IsNullOrEmpty(x.Notes));
|
|
}
|
|
|
|
}
|
|
|
|
public interface IWorkOrderVendorUpdateValidation : IValidator<WorkOrderVendorUpdateDTO>
|
|
{
|
|
}
|
|
|
|
public class WorkOrderVendorUpdateValidation : AbstractValidator<WorkOrderVendorUpdateDTO>, IWorkOrderVendorUpdateValidation
|
|
{
|
|
public WorkOrderVendorUpdateValidation()
|
|
{
|
|
RuleFor(x => x.WorkOrderId)
|
|
.GreaterThan(0).WithMessage("A valid work order id is required");
|
|
|
|
RuleFor(x => x.PreferredContact)
|
|
.Must(VendorValidationRules.BeValidPreferredContact)
|
|
.WithMessage("PreferredContact must be one of: Phone, Email, Text")
|
|
.When(x => !string.IsNullOrEmpty(x.PreferredContact));
|
|
|
|
RuleFor(x => x.Phone)
|
|
.Must(VendorValidationRules.BeValidNorthAmericanPhone)
|
|
.WithMessage("Phone must be in North American format: (XXX) XXX-XXXX")
|
|
.When(x => !string.IsNullOrEmpty(x.Phone));
|
|
|
|
RuleFor(x => x.Email)
|
|
.EmailAddress().WithMessage("Invalid email address")
|
|
.When(x => !string.IsNullOrEmpty(x.Email));
|
|
|
|
RuleFor(x => x.Notes)
|
|
.MaximumLength(2000).WithMessage("Notes cannot exceed 2000 characters")
|
|
.When(x => !string.IsNullOrEmpty(x.Notes));
|
|
|
|
RuleFor(x => x.ContactName)
|
|
.MaximumLength(200).WithMessage("Contact name cannot exceed 200 characters")
|
|
.When(x => !string.IsNullOrEmpty(x.ContactName));
|
|
}
|
|
}
|
|
|
|
internal static class VendorValidationRules
|
|
{
|
|
private static readonly string[] AllowedPreferredContacts = { "Phone", "Email", "Text" };
|
|
|
|
internal static bool BeValidHttpsUrl(string? value) =>
|
|
Uri.TryCreate(value, UriKind.Absolute, out var uri) && uri.Scheme == Uri.UriSchemeHttps;
|
|
|
|
internal static bool BeValidPreferredContact(string? value) =>
|
|
AllowedPreferredContacts.Contains(value, StringComparer.OrdinalIgnoreCase);
|
|
|
|
internal static bool BeValidNorthAmericanPhone(string? value) =>
|
|
value is not null && VendorPhoneNormalizer.NorthAmericanPhoneRegex.IsMatch(value);
|
|
}
|
|
}
|