From 456bee222651e9710b8e09d11f8984a831a25ac8 Mon Sep 17 00:00:00 2001 From: Arthur Bassi Date: Mon, 29 Jun 2026 17:22:31 -0300 Subject: [PATCH 01/14] feat(work-orders): add domain enums and aggregate root fields --- .../Enums/WorkOrderEnums.cs | 75 +++++++++++++++++++ Data.SeaHavenIndustries/Models/Dispatch.cs | 5 ++ .../Models/WorkOrderAuditLog.cs | 4 + .../Models/WorkOrderContacts.cs | 2 +- .../Models/WorkOrderFieldLock.cs | 18 +++++ Data.SeaHavenIndustries/Models/WorkerOrder.cs | 67 ++++++++++++----- 6 files changed, 153 insertions(+), 18 deletions(-) create mode 100644 Data.SeaHavenIndustries/Models/WorkOrderFieldLock.cs diff --git a/Data.SeaHavenIndustries/Enums/WorkOrderEnums.cs b/Data.SeaHavenIndustries/Enums/WorkOrderEnums.cs index 9c2e454..955b277 100644 --- a/Data.SeaHavenIndustries/Enums/WorkOrderEnums.cs +++ b/Data.SeaHavenIndustries/Enums/WorkOrderEnums.cs @@ -1,5 +1,6 @@ namespace Data.SeaHavenIndustries.Enums { + /// Legacy enum — prefer LifecycleStatus for board operations. public enum WorkOrderStatus { Open = 1, @@ -9,6 +10,72 @@ namespace Data.SeaHavenIndustries.Enums OnHold = 5 } + /// 10 operational lifecycle values aligned with SHOC board. + public enum LifecycleStatus + { + Incomplete = 1, + Scheduled = 2, + InProgress = 3, + Complete = 4, + Canceled = 5, + OnHold = 6, + PendingVendor = 7, + PendingApproval = 8, + NeedsReview = 9, + Closed = 10 + } + + public enum WorkOrderType + { + PO = 1, + PM = 2, + Emergency = 3, + Project = 4, + Inspection = 5, + Other = 99 + } + + public enum DocStatus + { + Yes = 1, + No = 2, + NotNeeded = 3 + } + + [Flags] + public enum OperationalFlags + { + None = 0, + PastDue = 1 + } + + public enum AuditEventType + { + Manual = 1, + System = 2, + Sync = 3, + Vendor = 4 + } + + public enum AuditActorType + { + Dispatcher = 1, + Vendor = 2, + System = 3, + Sync = 4 + } + + public enum AuditActionType + { + FieldChanged = 1, + StatusChanged = 2, + AssignmentChanged = 3, + WeekRolled = 4, + SyncRejected = 5, + Create = 6, + Delete = 7 + } + public enum WorkOrderPriority { Low = 1, @@ -89,4 +156,12 @@ namespace Data.SeaHavenIndustries.Enums Contact = 4, Asset = 5 } + + public enum WorkOrderMediaCategory + { + Before = 1, + After = 2, + Extra = 3, + Completion = 4 + } } diff --git a/Data.SeaHavenIndustries/Models/Dispatch.cs b/Data.SeaHavenIndustries/Models/Dispatch.cs index 869fedf..831cc17 100644 --- a/Data.SeaHavenIndustries/Models/Dispatch.cs +++ b/Data.SeaHavenIndustries/Models/Dispatch.cs @@ -1,3 +1,4 @@ +using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace Data.SeaHavenIndustries @@ -26,6 +27,10 @@ namespace Data.SeaHavenIndustries public string? AcceptToken { get; set; } public string? VerifiedBy { get; set; } public DateTime? VerifiedAt { get; set; } + + [Timestamp] + public byte[]? RowVersion { get; set; } + public ICollection? Comments { get; set; } public ICollection? DispatchWorkOrders { get; set; } public ICollection? ChecklistItems { get; set; } diff --git a/Data.SeaHavenIndustries/Models/WorkOrderAuditLog.cs b/Data.SeaHavenIndustries/Models/WorkOrderAuditLog.cs index 0b2572f..b3fbb7d 100644 --- a/Data.SeaHavenIndustries/Models/WorkOrderAuditLog.cs +++ b/Data.SeaHavenIndustries/Models/WorkOrderAuditLog.cs @@ -15,6 +15,10 @@ namespace Data.SeaHavenIndustries public string? OldValue { get; set; } public string? NewValue { get; set; } public string? Action { get; set; } + public string? EventType { get; set; } + public string? ActorType { get; set; } + public int? DispatchId { get; set; } + public string? CorrelationId { get; set; } public DateTime CreatedAt { get; set; } } } diff --git a/Data.SeaHavenIndustries/Models/WorkOrderContacts.cs b/Data.SeaHavenIndustries/Models/WorkOrderContacts.cs index 96e89eb..52919ea 100644 --- a/Data.SeaHavenIndustries/Models/WorkOrderContacts.cs +++ b/Data.SeaHavenIndustries/Models/WorkOrderContacts.cs @@ -12,6 +12,6 @@ namespace Data.SeaHavenIndustries public int? WorkorderId { get; set; } [ForeignKey(nameof(WorkorderId))] public virtual WorkOrder? WorkOrder { get; set; } - + public string? Notes { get; set; } } } diff --git a/Data.SeaHavenIndustries/Models/WorkOrderFieldLock.cs b/Data.SeaHavenIndustries/Models/WorkOrderFieldLock.cs new file mode 100644 index 0000000..c749159 --- /dev/null +++ b/Data.SeaHavenIndustries/Models/WorkOrderFieldLock.cs @@ -0,0 +1,18 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace Data.SeaHavenIndustries +{ + /// + /// ManualEditFlag — blocks Sync overwrite after first SHOC edit per field. + /// + public class WorkOrderFieldLock + { + public int Id { get; set; } + public int WorkOrderId { get; set; } + [ForeignKey(nameof(WorkOrderId))] + public virtual WorkOrder? WorkOrder { get; set; } + public string FieldName { get; set; } = ""; + public DateTime LockedAt { get; set; } + public string? LockedByUserId { get; set; } + } +} diff --git a/Data.SeaHavenIndustries/Models/WorkerOrder.cs b/Data.SeaHavenIndustries/Models/WorkerOrder.cs index 6d3692e..5bc5d4a 100644 --- a/Data.SeaHavenIndustries/Models/WorkerOrder.cs +++ b/Data.SeaHavenIndustries/Models/WorkerOrder.cs @@ -1,11 +1,16 @@ -using System; +using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using static System.Runtime.InteropServices.JavaScript.JSType; +using Data.SeaHavenIndustries.Enums; namespace Data.SeaHavenIndustries { + /// + /// Work Order aggregate root. Domain slices (Core, Scheduling, Tracking, Analytics, Completion) + /// are persisted as flat columns on workOrders for Fase 0 backward compatibility. + /// public class WorkOrder : FullAuditEntity { + // --- Core slice --- public string? InternalWONumber { get; set; } public string? ExternalWorkOrderId { get; set; } public string? WorkerOrderNumber { get; set; } @@ -15,34 +20,57 @@ namespace Data.SeaHavenIndustries public string? SiteCode { get; set; } public string? Building { get; set; } public string? Severity { get; set; } - public DateTime? DateReported { get; set; } + public DateTime? DueDate { get; set; } + public string? AssignTo { get; set; } + [ForeignKey(nameof(AssignTo))] + public virtual ApplicationUser? AssignToUser { get; set; } + public int? LocationId { get; set; } + [ForeignKey(nameof(LocationId))] + public virtual Locations? Locations { get; set; } + public LifecycleStatus? LifecycleStatus { get; set; } + public string? LegacyStatus { get; set; } + public WorkOrderType? WorkOrderType { get; set; } + public int? PrimaryDispatchId { get; set; } + [ForeignKey(nameof(PrimaryDispatchId))] + public virtual Dispatch? PrimaryDispatch { get; set; } + + // --- Scheduling slice --- + public DateTime? ScheduledDate { get; set; } public DateTime? ScheduledStart { get; set; } + public DateTime? ScheduledEnd { get; set; } + public DateOnly? TargetWeek { get; set; } + public bool? ScheduleWeekOnly { get; set; } + + // --- Tracking slice (set-once) --- + public DateOnly? OriginalWeek { get; set; } + public DateOnly? OriginalDate { get; set; } + + // --- Analytics slice --- + public int RescheduleCount { get; set; } + public int CarriedOver { get; set; } + public OperationalFlags OperationalFlags { get; set; } + + // --- Completion slice --- + public DocStatus? DocStatus { get; set; } + + // --- Legacy SQL columns (drift reconciliation) --- + public string? AvettaTask { get; set; } + public DateOnly? AssignDate { get; set; } + + // --- Legacy fields (retained for Blazor / coexistence) --- + public DateTime? DateReported { get; set; } public string? SourceEmailS3Key { get; set; } public string? Problem { get; set; } public string? Trade { get; set; } public string? SubTrade { get; set; } public decimal? VendorNTE { get; set; } - public DateTime? ScheduledDate { get; set; } public DateTime? CompletedDate { get; set; } public string? Source { get; set; } public bool? istemplate { get; set; } public string? PO { get; set; } public string? TT { get; set; } - //public int? ContactId { get; set; } - //[ForeignKey(nameof(ContactId))] - //public virtual Contacts? POC { get; set; } - public string? AssignTo { get; set; } - [ForeignKey(nameof(AssignTo))] - public virtual ApplicationUser? AssignToUser { get; set; } - public DateTime? DueDate { get; set; } public string? Priority { get; set; } public string? Status { get; set; } - public int? LocationId { get; set; } - [ForeignKey(nameof(LocationId))] - public virtual Locations? Locations { get; set; } - //public int? CategoryId { get; set; } - //[ForeignKey(nameof(CategoryId))] - //public virtual Category? Category { get; set; } public string? Attachments { get; set; } public string? BeforPhotoIssue { get; set; } public string? BeforPhotoAttachment { get; set; } @@ -51,11 +79,16 @@ namespace Data.SeaHavenIndustries public string? SignOffName { get; set; } public string? SignOffAttachment { get; set; } public string? SignOffSignature { get; set; } + + [Timestamp] + public byte[]? RowVersion { get; set; } + public ICollection? Comments { get; set; } public ICollection? Quotes { get; set; } public ICollection? WorkOrderContacts { get; set; } public ICollection? workOrderAttachments { get; set; } public ICollection? WorkOrderCategories { get; set; } public ICollection? Dispatches { get; set; } + public ICollection? FieldLocks { get; set; } } } From 35b3bba596172bfda91b7364e1053282e00f6a08 Mon Sep 17 00:00:00 2001 From: Arthur Bassi Date: Mon, 29 Jun 2026 17:22:31 -0300 Subject: [PATCH 02/14] feat(work-orders): add EF config and Phase 0 migration --- .../Auth/ApplicationDbContext.cs | 21 + ...163145_Phase0_DomainFoundation.Designer.cs | 2987 +++++++++++++++++ .../20260624163145_Phase0_DomainFoundation.cs | 733 ++++ .../ApplicationDbContextModelSnapshot.cs | 245 +- 4 files changed, 3877 insertions(+), 109 deletions(-) create mode 100644 Data.SeaHavenIndustries/Migrations/20260624163145_Phase0_DomainFoundation.Designer.cs create mode 100644 Data.SeaHavenIndustries/Migrations/20260624163145_Phase0_DomainFoundation.cs diff --git a/Data.SeaHavenIndustries/Auth/ApplicationDbContext.cs b/Data.SeaHavenIndustries/Auth/ApplicationDbContext.cs index d66f708..40a6842 100644 --- a/Data.SeaHavenIndustries/Auth/ApplicationDbContext.cs +++ b/Data.SeaHavenIndustries/Auth/ApplicationDbContext.cs @@ -33,6 +33,24 @@ namespace Data.SeaHavenIndustries .HasIndex(u => new { u.DispatchId, u.Status }) .IsUnique() .HasFilter("[Status] = 'Pending'"); + + builder.Entity() + .HasIndex(l => new { l.WorkOrderId, l.FieldName }) + .IsUnique(); + + builder.Entity() + .Property(w => w.RowVersion) + .IsRowVersion(); + + builder.Entity() + .Property(d => d.RowVersion) + .IsRowVersion(); + + builder.Entity() + .HasOne(w => w.PrimaryDispatch) + .WithMany() + .HasForeignKey(w => w.PrimaryDispatchId) + .OnDelete(DeleteBehavior.Restrict); } public DbSet Categories { get; set; } public DbSet Locations { get; set; } @@ -47,6 +65,7 @@ namespace Data.SeaHavenIndustries public DbSet workOrderCategories { get; set; } public DbSet workOrderAttachments { get; set; } public DbSet WorkOrderAuditLogs { get; set; } + public DbSet WorkOrderFieldLocks { get; set; } public DbSet DropdownOptions { get; set; } public DbSet Vendors { get; set; } public DbSet VendorAccessTokens { get; set; } @@ -87,6 +106,8 @@ namespace Data.SeaHavenIndustries public DateTime? DeletedDate { get; set; } public bool? IsDeleted { get; set; } public string? UserImage { get; set; } + public string? Initials { get; set; } + public string? Color { get; set; } public int? Type { get; set; } // 1 for users 0 for admin public ICollection