mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-10-01 13:23:13 +00:00
* refactor(api): enforce service and data-service boundaries * refactor(api): complete feature service boundaries * refactor(identity): enforce service and data boundaries * refactor(vendors): enforce service and data boundaries * refactor(workorders): enforce service and data boundaries * refactor(backend): enforce architecture and optimize dispatch * style(backend): format changed architecture files * fix(architecture): address backend review follow-ups * fix(backend): sanitize exception disclosure in changed API endpoints Replace raw exception-message disclosure (ex.Message) returned to API callers with a stable sanitized public message plus correlated structured internal logging, across the endpoints changed in this PR. - Add SanitizedErrors helper: logs the original exception at Error with a generated correlation id and returns a stable public message referencing it so support can trace without exposing internals. - Inject ILogger<T> into the 14 changed controllers and route every ex.Message/dbex.Message disclosure through the helper, preserving status codes, response shapes, and business data (e.g. OpenWorkOrders). - Leave FluentValidation (vex.Errors) and existing fixed-message catches untouched; out-of-scope controllers (Account/Contact/Employee/Asset/ PMSchedule) are unchanged. - Add focused tests proving internal exception text is not returned and that Error logging carrying the original exception is invoked. * fix(architecture): abstract job run state access * style: format board update service * test: use collection assertion idiom
155 lines
6 KiB
C#
155 lines
6 KiB
C#
namespace SeaHaven.Services.DTOs
|
|
{
|
|
public enum VendorOperationOutcomeKind { Ok, NotFound, BadRequest, Conflict }
|
|
|
|
public sealed class VendorOperationOutcome<T>
|
|
{
|
|
public VendorOperationOutcomeKind Kind { get; init; }
|
|
public T? Value { get; init; }
|
|
public string? Message { get; init; }
|
|
|
|
public static VendorOperationOutcome<T> Ok(T value) => new() { Kind = VendorOperationOutcomeKind.Ok, Value = value };
|
|
public static VendorOperationOutcome<T> NotFound(string? message = null) => new() { Kind = VendorOperationOutcomeKind.NotFound, Message = message };
|
|
public static VendorOperationOutcome<T> BadRequest(string message) => new() { Kind = VendorOperationOutcomeKind.BadRequest, Message = message };
|
|
public static VendorOperationOutcome<T> Conflict(string message) => new() { Kind = VendorOperationOutcomeKind.Conflict, Message = message };
|
|
}
|
|
|
|
public abstract class VendorNotificationItem
|
|
{
|
|
public DateTime TriggeredAt { get; set; }
|
|
}
|
|
|
|
public sealed class NotificationActionDto
|
|
{
|
|
public string Label { get; set; } = "";
|
|
public string Href { get; set; } = "";
|
|
}
|
|
|
|
public sealed class NoVendorNotification : VendorNotificationItem
|
|
{
|
|
public string Id { get; set; } = "";
|
|
public string Type { get; set; } = "";
|
|
public string Severity { get; set; } = "";
|
|
public string Group { get; set; } = "";
|
|
public string Title { get; set; } = "";
|
|
public int WorkOrderId { get; set; }
|
|
public string? WorkOrderNumber { get; set; }
|
|
public DateTime ScheduledAt { get; set; }
|
|
public NotificationActionDto Action { get; set; } = new();
|
|
}
|
|
|
|
public sealed class ConflictWorkOrderDto
|
|
{
|
|
public int WorkOrderId { get; set; }
|
|
public string? WorkOrderNumber { get; set; }
|
|
public DateTime Start { get; set; }
|
|
public DateTime End { get; set; }
|
|
}
|
|
|
|
public sealed class VendorConflictNotification : VendorNotificationItem
|
|
{
|
|
public string Id { get; set; } = "";
|
|
public string Type { get; set; } = "";
|
|
public string Severity { get; set; } = "";
|
|
public string Group { get; set; } = "";
|
|
public string Title { get; set; } = "";
|
|
public int VendorId { get; set; }
|
|
public string? VendorName { get; set; }
|
|
public IReadOnlyList<ConflictWorkOrderDto> WorkOrders { get; set; } = Array.Empty<ConflictWorkOrderDto>();
|
|
public IReadOnlyList<NotificationActionDto> Actions { get; set; } = Array.Empty<NotificationActionDto>();
|
|
}
|
|
|
|
public sealed class VendorNotificationsResult
|
|
{
|
|
public DateTime GeneratedAt { get; set; }
|
|
public IReadOnlyList<VendorNotificationItem> Items { get; set; } = Array.Empty<VendorNotificationItem>();
|
|
}
|
|
|
|
public sealed class AvailabilityConflictDto
|
|
{
|
|
public int? WorkOrderId { get; set; }
|
|
public string? WorkOrderNumber { get; set; }
|
|
public DateTime Start { get; set; }
|
|
public DateTime End { get; set; }
|
|
}
|
|
|
|
public sealed class VendorAvailabilityRow
|
|
{
|
|
public int Id { get; set; }
|
|
public string? CompanyName { get; set; }
|
|
public string? ContactName { get; set; }
|
|
public string? TradeSpecialties { get; set; }
|
|
public string AvailabilityStatus { get; set; } = "";
|
|
public DateTime? AvailabilityUpdatedAt { get; set; }
|
|
public bool IsPreferred { get; set; }
|
|
public int? PreferenceRank { get; set; }
|
|
public IReadOnlyList<AvailabilityConflictDto> Conflicts { get; set; } = Array.Empty<AvailabilityConflictDto>();
|
|
}
|
|
|
|
public sealed class VendorAvailabilityResult
|
|
{
|
|
public DateTime GeneratedAt { get; set; }
|
|
public int StaleAfterHours { get; set; }
|
|
public IReadOnlyList<VendorAvailabilityRow> Data { get; set; } = Array.Empty<VendorAvailabilityRow>();
|
|
}
|
|
|
|
public sealed class VendorAvailabilityUpdateResult
|
|
{
|
|
public int Id { get; set; }
|
|
public string? AvailabilityStatus { get; set; }
|
|
public DateTime? AvailabilityUpdatedAt { get; set; }
|
|
}
|
|
|
|
public sealed class SitePreferenceRow
|
|
{
|
|
public int Id { get; set; }
|
|
public int LocationId { get; set; }
|
|
public int VendorId { get; set; }
|
|
public string? VendorName { get; set; }
|
|
public string? Trade { get; set; }
|
|
public int? SortOrder { get; set; }
|
|
public bool IsStale { get; set; }
|
|
public DateTime? LastModificationTime { get; set; }
|
|
public string? UpdatedBy { get; set; }
|
|
}
|
|
|
|
public sealed class DocumentReviewResult
|
|
{
|
|
public int Id { get; set; }
|
|
public string? ReviewStatus { get; set; }
|
|
public string? RejectionReason { get; set; }
|
|
public DateTime? ReviewedAt { get; set; }
|
|
}
|
|
|
|
public sealed class CommercialStatusResult
|
|
{
|
|
public int Id { get; set; }
|
|
public string? PONumber { get; set; }
|
|
public string? InvoiceNumber { get; set; }
|
|
public string? InvoiceStatus { get; set; }
|
|
public string? PaymentStatus { get; set; }
|
|
public DateTime? CommercialStatusUpdatedAt { get; set; }
|
|
}
|
|
|
|
public sealed class VendorAssignmentResult
|
|
{
|
|
public int Id { get; set; }
|
|
public int VendorId { get; set; }
|
|
public string? VendorName { get; set; }
|
|
public DateTime? EstimatedArrivalAt { get; set; }
|
|
public bool EtaManualOverride { get; set; }
|
|
public string AvailabilityStatus { get; set; } = "";
|
|
public IReadOnlyList<AvailabilityConflictDto> Conflicts { get; set; } = Array.Empty<AvailabilityConflictDto>();
|
|
}
|
|
|
|
public sealed class VendorInsightRow
|
|
{
|
|
public int VendorId { get; set; }
|
|
public string Vendor { get; set; } = "";
|
|
public int CompletedJobs { get; set; }
|
|
public decimal CancellationRefusalRate { get; set; }
|
|
public decimal OnTimeArrivalRate { get; set; }
|
|
public decimal OnTimeCompletionRate { get; set; }
|
|
public double? AverageCycleHours { get; set; }
|
|
}
|
|
}
|