shoc-backend/SeaHaven.Services/DTOs/VendorOperationsResults.cs
Alexandre Brandizzi 7d245eb717
refactor: enforce backend boundaries and optimize dispatch (#30)
* 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
2026-07-24 17:35:34 -03:00

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; }
}
}