diff --git a/Api.SeaHavenIndustries/Controllers/WorkOrderJobsController.cs b/Api.SeaHavenIndustries/Controllers/WorkOrderJobsController.cs
new file mode 100644
index 0000000..4277475
--- /dev/null
+++ b/Api.SeaHavenIndustries/Controllers/WorkOrderJobsController.cs
@@ -0,0 +1,58 @@
+using Api.SeaHavenIndustries.Options;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using SeaHaven.Services.DTOs;
+using SeaHaven.Services.Helpers;
+using SeaHaven.Services.Interfaces;
+
+namespace Api.SeaHavenIndustries.Controllers
+{
+ /// Ops-only endpoints for manual domain job reprocessing.
+ [Authorize(Roles = "Admin")]
+ [ApiController]
+ [Route("api/workorders/jobs")]
+ public class WorkOrderJobsController : ControllerBase
+ {
+ private readonly IWorkOrderWeekRolledService _weekRolledService;
+ private readonly IPastDueCacheService _pastDueCacheService;
+
+ public WorkOrderJobsController(
+ IWorkOrderWeekRolledService weekRolledService,
+ IPastDueCacheService pastDueCacheService)
+ {
+ _weekRolledService = weekRolledService;
+ _pastDueCacheService = pastDueCacheService;
+ }
+
+ ///
+ /// Reprocess WeekRolled for a specific operational week (Monday start).
+ /// Idempotent via WorkOrderWeekRolledLedger.
+ ///
+ [HttpPost("week-rolled")]
+ [ProducesResponseType(typeof(WeekRolledJobResult), StatusCodes.Status200OK)]
+ public async Task> RunWeekRolled(
+ [FromQuery] DateOnly? sourceWeekStart,
+ CancellationToken cancellationToken)
+ {
+ var weekStart = sourceWeekStart
+ ?? WorkOrderOperationalWeek.GetPreviousOperationalWeekStart(DateTime.UtcNow);
+
+ if (weekStart.DayOfWeek != DayOfWeek.Monday)
+ return BadRequest("sourceWeekStart must be a Monday (operational week start).");
+
+ var result = await _weekRolledService.ProcessWeekRolledAsync(weekStart, cancellationToken);
+ return Ok(result);
+ }
+
+ ///
+ /// Refresh optional OperationalFlags.PastDue cache (board isPastDue remains on-read).
+ ///
+ [HttpPost("past-due-cache")]
+ [ProducesResponseType(typeof(PastDueCacheJobResult), StatusCodes.Status200OK)]
+ public async Task> RunPastDueCache(CancellationToken cancellationToken)
+ {
+ var result = await _pastDueCacheService.RefreshAsync(cancellationToken);
+ return Ok(result);
+ }
+ }
+}
diff --git a/Api.SeaHavenIndustries/HostedServices/PastDueCacheHostedService.cs b/Api.SeaHavenIndustries/HostedServices/PastDueCacheHostedService.cs
new file mode 100644
index 0000000..67b8cba
--- /dev/null
+++ b/Api.SeaHavenIndustries/HostedServices/PastDueCacheHostedService.cs
@@ -0,0 +1,74 @@
+using Api.SeaHavenIndustries.Options;
+using Microsoft.Extensions.Options;
+using SeaHaven.Services.Interfaces;
+
+namespace Api.SeaHavenIndustries.HostedServices
+{
+ public class PastDueCacheHostedService : BackgroundService
+ {
+ private readonly IServiceScopeFactory _scopeFactory;
+ private readonly ILogger _logger;
+ private readonly PastDueCacheJobOptions _options;
+ private readonly WorkOrderJobRunState _runState;
+ private DateOnly? _lastRunDate;
+
+ public PastDueCacheHostedService(
+ IServiceScopeFactory scopeFactory,
+ IOptions options,
+ WorkOrderJobRunState runState,
+ ILogger logger)
+ {
+ _scopeFactory = scopeFactory;
+ _logger = logger;
+ _options = options.Value.PastDueCache;
+ _runState = runState;
+ }
+
+ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
+ {
+ while (!stoppingToken.IsCancellationRequested)
+ {
+ if (_options.Enabled && ShouldRunNow())
+ {
+ var today = DateOnly.FromDateTime(DateTime.UtcNow);
+ if (_lastRunDate != today)
+ {
+ _lastRunDate = today;
+ await RunJobAsync(stoppingToken);
+ }
+ }
+
+ await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
+ }
+ }
+
+ private async Task RunJobAsync(CancellationToken stoppingToken)
+ {
+ try
+ {
+ using var scope = _scopeFactory.CreateScope();
+ var service = scope.ServiceProvider.GetRequiredService();
+ await service.RefreshAsync(stoppingToken);
+
+ _runState.LastPastDueCacheRunUtc = DateTime.UtcNow;
+ _runState.LastPastDueCacheError = null;
+ }
+ catch (Exception ex) when (ex is not OperationCanceledException)
+ {
+ _runState.LastPastDueCacheError = ex.Message;
+ _logger.LogError(ex, "PastDue cache hosted job failed.");
+ }
+ }
+
+ private bool ShouldRunNow()
+ {
+ var now = DateTime.UtcNow;
+
+ if (!TimeOnly.TryParse(_options.RunAtUtc, out var runAt))
+ runAt = new TimeOnly(0, 10);
+
+ var current = TimeOnly.FromDateTime(now);
+ return current >= runAt && current < runAt.AddMinutes(1);
+ }
+ }
+}
diff --git a/Api.SeaHavenIndustries/HostedServices/WorkOrderWeekRolledHostedService.cs b/Api.SeaHavenIndustries/HostedServices/WorkOrderWeekRolledHostedService.cs
new file mode 100644
index 0000000..7caf0a2
--- /dev/null
+++ b/Api.SeaHavenIndustries/HostedServices/WorkOrderWeekRolledHostedService.cs
@@ -0,0 +1,82 @@
+using Api.SeaHavenIndustries.Options;
+using Microsoft.Extensions.Options;
+using SeaHaven.Services.Helpers;
+using SeaHaven.Services.Interfaces;
+
+namespace Api.SeaHavenIndustries.HostedServices
+{
+ public class WorkOrderWeekRolledHostedService : BackgroundService
+ {
+ private readonly IServiceScopeFactory _scopeFactory;
+ private readonly ILogger _logger;
+ private readonly WeekRolledJobOptions _options;
+ private readonly WorkOrderJobRunState _runState;
+ private DateOnly? _lastRunDate;
+
+ public WorkOrderWeekRolledHostedService(
+ IServiceScopeFactory scopeFactory,
+ IOptions options,
+ WorkOrderJobRunState runState,
+ ILogger logger)
+ {
+ _scopeFactory = scopeFactory;
+ _logger = logger;
+ _options = options.Value.WeekRolled;
+ _runState = runState;
+ }
+
+ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
+ {
+ while (!stoppingToken.IsCancellationRequested)
+ {
+ if (_options.Enabled && ShouldRunNow())
+ {
+ var today = DateOnly.FromDateTime(DateTime.UtcNow);
+ if (_lastRunDate != today)
+ {
+ _lastRunDate = today;
+ await RunJobAsync(stoppingToken);
+ }
+ }
+
+ await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
+ }
+ }
+
+ private async Task RunJobAsync(CancellationToken stoppingToken)
+ {
+ try
+ {
+ var sourceWeekStart = WorkOrderOperationalWeek.GetPreviousOperationalWeekStart(DateTime.UtcNow);
+
+ using var scope = _scopeFactory.CreateScope();
+ var service = scope.ServiceProvider.GetRequiredService();
+ await service.ProcessWeekRolledAsync(sourceWeekStart, stoppingToken);
+
+ _runState.LastWeekRolledRunUtc = DateTime.UtcNow;
+ _runState.LastWeekRolledError = null;
+ }
+ catch (Exception ex) when (ex is not OperationCanceledException)
+ {
+ _runState.LastWeekRolledError = ex.Message;
+ _logger.LogError(ex, "WeekRolled hosted job failed.");
+ }
+ }
+
+ private bool ShouldRunNow()
+ {
+ var now = DateTime.UtcNow;
+ if (!Enum.TryParse(_options.DayOfWeek, ignoreCase: true, out var targetDay))
+ targetDay = DayOfWeek.Monday;
+
+ if (now.DayOfWeek != targetDay)
+ return false;
+
+ if (!TimeOnly.TryParse(_options.RunAtUtc, out var runAt))
+ runAt = new TimeOnly(0, 5);
+
+ var current = TimeOnly.FromDateTime(now);
+ return current >= runAt && current < runAt.AddMinutes(1);
+ }
+ }
+}
diff --git a/Api.SeaHavenIndustries/Options/WorkOrderJobRunState.cs b/Api.SeaHavenIndustries/Options/WorkOrderJobRunState.cs
new file mode 100644
index 0000000..dd95de5
--- /dev/null
+++ b/Api.SeaHavenIndustries/Options/WorkOrderJobRunState.cs
@@ -0,0 +1,11 @@
+namespace Api.SeaHavenIndustries.Options
+{
+ /// In-memory last-run timestamps for hosted domain jobs (ops health).
+ public class WorkOrderJobRunState
+ {
+ public DateTime? LastWeekRolledRunUtc { get; set; }
+ public DateTime? LastPastDueCacheRunUtc { get; set; }
+ public string? LastWeekRolledError { get; set; }
+ public string? LastPastDueCacheError { get; set; }
+ }
+}
diff --git a/Api.SeaHavenIndustries/Options/WorkOrderJobsOptions.cs b/Api.SeaHavenIndustries/Options/WorkOrderJobsOptions.cs
new file mode 100644
index 0000000..791638c
--- /dev/null
+++ b/Api.SeaHavenIndustries/Options/WorkOrderJobsOptions.cs
@@ -0,0 +1,23 @@
+namespace Api.SeaHavenIndustries.Options
+{
+ public class WorkOrderJobsOptions
+ {
+ public const string SectionName = "WorkOrderJobs";
+
+ public WeekRolledJobOptions WeekRolled { get; set; } = new();
+ public PastDueCacheJobOptions PastDueCache { get; set; } = new();
+ }
+
+ public class WeekRolledJobOptions
+ {
+ public bool Enabled { get; set; } = true;
+ public string RunAtUtc { get; set; } = "00:05";
+ public string DayOfWeek { get; set; } = "Monday";
+ }
+
+ public class PastDueCacheJobOptions
+ {
+ public bool Enabled { get; set; }
+ public string RunAtUtc { get; set; } = "00:10";
+ }
+}
diff --git a/Api.SeaHavenIndustries/Program.cs b/Api.SeaHavenIndustries/Program.cs
index d7efbeb..1661e8d 100644
--- a/Api.SeaHavenIndustries/Program.cs
+++ b/Api.SeaHavenIndustries/Program.cs
@@ -1,4 +1,6 @@
using Api.SeaHavenIndustries.Helper;
+using Api.SeaHavenIndustries.HostedServices;
+using Api.SeaHavenIndustries.Options;
using Data.SeaHavenIndustries;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Identity;
@@ -55,6 +57,12 @@ builder.Services.AddSingleton(sp
builder.Services.AddDataServices();
builder.Services.AddBusinessServices();
+builder.Services.Configure(
+ builder.Configuration.GetSection(WorkOrderJobsOptions.SectionName));
+builder.Services.AddSingleton();
+builder.Services.AddHostedService();
+builder.Services.AddHostedService();
+
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
diff --git a/Api.SeaHavenIndustries/appsettings.json b/Api.SeaHavenIndustries/appsettings.json
index 426d85b..3c6dd39 100644
--- a/Api.SeaHavenIndustries/appsettings.json
+++ b/Api.SeaHavenIndustries/appsettings.json
@@ -30,5 +30,9 @@
"ValidIssuer": "http://localhost:7195",
// Provide the real value via environment variable JWT__Secret or user-secrets.
"Secret": "${JWT_SECRET}"
+ },
+ "WorkOrderJobs": {
+ "WeekRolled": { "Enabled": true, "RunAtUtc": "00:05", "DayOfWeek": "Monday" },
+ "PastDueCache": { "Enabled": false, "RunAtUtc": "00:10" }
}
}
\ No newline at end of file
diff --git a/Data.SeaHavenIndustries/Auth/ApplicationDbContext.cs b/Data.SeaHavenIndustries/Auth/ApplicationDbContext.cs
index 136e352..699dd15 100644
--- a/Data.SeaHavenIndustries/Auth/ApplicationDbContext.cs
+++ b/Data.SeaHavenIndustries/Auth/ApplicationDbContext.cs
@@ -38,6 +38,10 @@ namespace Data.SeaHavenIndustries
.HasIndex(l => new { l.WorkOrderId, l.FieldName })
.IsUnique();
+ builder.Entity()
+ .HasIndex(l => new { l.WorkOrderId, l.SourceWeekStart })
+ .IsUnique();
+
builder.Entity()
.Property(w => w.RowVersion)
.IsRowVersion();
@@ -108,6 +112,7 @@ namespace Data.SeaHavenIndustries
public DbSet workOrderAttachments { get; set; }
public DbSet WorkOrderAuditLogs { get; set; }
public DbSet WorkOrderFieldLocks { get; set; }
+ public DbSet WorkOrderWeekRolledLedgers { get; set; }
public DbSet DropdownOptions { get; set; }
public DbSet Vendors { get; set; }
public DbSet VendorCompanies { get; set; }
diff --git a/Data.SeaHavenIndustries/Migrations/20260709170202_Phase5_DomainEvents.Designer.cs b/Data.SeaHavenIndustries/Migrations/20260709170202_Phase5_DomainEvents.Designer.cs
new file mode 100644
index 0000000..7387556
--- /dev/null
+++ b/Data.SeaHavenIndustries/Migrations/20260709170202_Phase5_DomainEvents.Designer.cs
@@ -0,0 +1,3048 @@
+//
+using System;
+using Data.SeaHavenIndustries;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace Data.SeaHavenIndustries.Migrations
+{
+ [DbContext(typeof(ApplicationDbContext))]
+ [Migration("20260709170202_Phase5_DomainEvents")]
+ partial class Phase5_DomainEvents
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "8.0.8")
+ .HasAnnotation("Relational:MaxIdentifierLength", 128);
+
+ SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
+
+ modelBuilder.Entity("Data.SeaHavenIndustries.Accounts", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("About")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("CreatedDate")
+ .HasColumnType("datetime2");
+
+ b.Property("CurrencyCode")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("DeleterUserId")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("DeletionTime")
+ .HasColumnType("datetime2");
+
+ b.Property("Email")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("FacebookUrl")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Importance")
+ .HasColumnType("int");
+
+ b.Property("IndustryId")
+ .HasColumnType("int");
+
+ b.Property("IsDeleted")
+ .HasColumnType("bit");
+
+ b.Property("LastModificationTime")
+ .HasColumnType("datetime2");
+
+ b.Property("LastModifierUserId")
+ .HasColumnType("int");
+
+ b.Property("LinkedInUrl")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Name")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("OwnerEmployeeId")
+ .HasColumnType("int");
+
+ b.Property("Phone")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("PrivacySetting")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("ServiceInstructions")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Tags")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("TerritoryId")
+ .HasColumnType("int");
+
+ b.Property("TimeZone")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("TwitterUrl")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Type")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Website")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("createdby")
+ .HasColumnType("nvarchar(max)");
+
+ b.HasKey("Id");
+
+ b.ToTable("Accounts", (string)null);
+ });
+
+ modelBuilder.Entity("Data.SeaHavenIndustries.Addresses", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("AccountId")
+ .HasColumnType("int");
+
+ b.Property("Address1")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Address2")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("AddressType")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("City")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("ContactId")
+ .HasColumnType("int");
+
+ b.Property("Country")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("County")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("CreatedDate")
+ .HasColumnType("datetime2");
+
+ b.Property("DeleterUserId")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("DeletionTime")
+ .HasColumnType("datetime2");
+
+ b.Property("IsDeleted")
+ .HasColumnType("bit");
+
+ b.Property("LastModificationTime")
+ .HasColumnType("datetime2");
+
+ b.Property("LastModifierUserId")
+ .HasColumnType("int");
+
+ b.Property("State")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Zip")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("createdby")
+ .HasColumnType("nvarchar(max)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AccountId");
+
+ b.HasIndex("ContactId");
+
+ b.ToTable("Addresses", (string)null);
+ });
+
+ modelBuilder.Entity("Data.SeaHavenIndustries.ApplicationUser", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("AccessFailedCount")
+ .HasColumnType("int");
+
+ b.Property("Color")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Contact")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("CreatedDate")
+ .HasColumnType("datetime2");
+
+ b.Property("DeletedDate")
+ .HasColumnType("datetime2");
+
+ b.Property("Email")
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.Property("EmailConfirmed")
+ .HasColumnType("bit");
+
+ b.Property("FirstName")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Initials")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("IsDeleted")
+ .HasColumnType("bit");
+
+ b.Property("LastName")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Locations")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("LockoutEnabled")
+ .HasColumnType("bit");
+
+ b.Property("LockoutEnd")
+ .HasColumnType("datetimeoffset");
+
+ b.Property("NormalizedEmail")
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.Property("NormalizedUserName")
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.Property("PasswordHash")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("PhoneNumber")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("PhoneNumberConfirmed")
+ .HasColumnType("bit");
+
+ b.Property("SecurityStamp")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("TwoFactorEnabled")
+ .HasColumnType("bit");
+
+ b.Property("Type")
+ .HasColumnType("int");
+
+ b.Property("UniqueName")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("UserImage")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("UserName")
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedEmail")
+ .HasDatabaseName("EmailIndex");
+
+ b.HasIndex("NormalizedUserName")
+ .IsUnique()
+ .HasDatabaseName("UserNameIndex")
+ .HasFilter("[NormalizedUserName] IS NOT NULL");
+
+ b.ToTable("AspNetUsers", (string)null);
+ });
+
+ modelBuilder.Entity("Data.SeaHavenIndustries.Assets", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("AccountId")
+ .HasColumnType("int");
+
+ b.Property("AssetCode")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("CreatedDate")
+ .HasColumnType("datetime2");
+
+ b.Property("DeleterUserId")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("DeletionTime")
+ .HasColumnType("datetime2");
+
+ b.Property("Description")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("IsActive")
+ .HasColumnType("bit");
+
+ b.Property("IsDeleted")
+ .HasColumnType("bit");
+
+ b.Property("LastModificationTime")
+ .HasColumnType("datetime2");
+
+ b.Property("LastModifierUserId")
+ .HasColumnType("int");
+
+ b.Property("LocationId")
+ .HasColumnType("int");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("createdby")
+ .HasColumnType("nvarchar(max)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AccountId");
+
+ b.HasIndex("LocationId");
+
+ b.ToTable("Assets", (string)null);
+ });
+
+ modelBuilder.Entity("Data.SeaHavenIndustries.Category", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("CreatedDate")
+ .HasColumnType("datetime2");
+
+ b.Property("DeleterUserId")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("DeletionTime")
+ .HasColumnType("datetime2");
+
+ b.Property("IsDeleted")
+ .HasColumnType("bit");
+
+ b.Property("LastModificationTime")
+ .HasColumnType("datetime2");
+
+ b.Property("LastModifierUserId")
+ .HasColumnType("int");
+
+ b.Property("Name")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("createdby")
+ .HasColumnType("nvarchar(max)");
+
+ b.HasKey("Id");
+
+ b.ToTable("Categories", (string)null);
+ });
+
+ modelBuilder.Entity("Data.SeaHavenIndustries.Comments", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("CommentType")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Commenter")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Commenttext")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("CreatedDate")
+ .HasColumnType("datetime2");
+
+ b.Property("DeleterUserId")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("DeletionTime")
+ .HasColumnType("datetime2");
+
+ b.Property("DispatchId")
+ .HasColumnType("int");
+
+ b.Property("Documents")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("ExternalCommentId")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("IsDeleted")
+ .HasColumnType("bit");
+
+ b.Property("LastModificationTime")
+ .HasColumnType("datetime2");
+
+ b.Property("LastModifierUserId")
+ .HasColumnType("int");
+
+ b.Property("RecordType")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("UserId")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("WorkerOrderId")
+ .HasColumnType("int");
+
+ b.Property("createdby")
+ .HasColumnType("nvarchar(max)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("DispatchId");
+
+ b.HasIndex("UserId");
+
+ b.HasIndex("WorkerOrderId");
+
+ b.ToTable("Comments", (string)null);
+ });
+
+ modelBuilder.Entity("Data.SeaHavenIndustries.ContactDetails", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("About")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("ContactId")
+ .HasColumnType("int");
+
+ b.Property("CreatedDate")
+ .HasColumnType("datetime2");
+
+ b.Property("DateOfBirth")
+ .HasColumnType("datetime2");
+
+ b.Property("DeleterUserId")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("DeletionTime")
+ .HasColumnType("datetime2");
+
+ b.Property("FacebookUrl")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Gender")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("IsDeleted")
+ .HasColumnType("bit");
+
+ b.Property("LastModificationTime")
+ .HasColumnType("datetime2");
+
+ b.Property("LastModifierUserId")
+ .HasColumnType("int");
+
+ b.Property("LinkedInUrl")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("PrivacySetting")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Tags")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Territory")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("TwitterUrl")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("createdby")
+ .HasColumnType("nvarchar(max)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ContactId");
+
+ b.ToTable("ContactDetails", (string)null);
+ });
+
+ modelBuilder.Entity("Data.SeaHavenIndustries.Contacts", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("AccountId")
+ .HasColumnType("int");
+
+ b.Property("Address1")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Address2")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("City")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("ContactType")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("CreatedDate")
+ .HasColumnType("datetime2");
+
+ b.Property("DeleterUserId")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("DeletionTime")
+ .HasColumnType("datetime2");
+
+ b.Property("Email")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("FacebookUrl")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("FirstName")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("IsDeleted")
+ .HasColumnType("bit");
+
+ b.Property("LastModificationTime")
+ .HasColumnType("datetime2");
+
+ b.Property("LastModifierUserId")
+ .HasColumnType("int");
+
+ b.Property("LastName")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("LinkedInUrl")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("LocationId")
+ .HasColumnType("int");
+
+ b.Property("MiddleName")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Owner")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("PhoneNumber")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("State")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Title")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("TwitterUrl")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Zip")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("createdby")
+ .HasColumnType("nvarchar(max)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AccountId");
+
+ b.HasIndex("LocationId");
+
+ b.ToTable("Contacts", (string)null);
+ });
+
+ modelBuilder.Entity("Data.SeaHavenIndustries.Department", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("nvarchar(200)");
+
+ b.HasKey("Id");
+
+ b.ToTable("Departments", (string)null);
+ });
+
+ modelBuilder.Entity("Data.SeaHavenIndustries.Dispatch", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("AcceptToken")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("AcknowledgedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("CompletedDate")
+ .HasColumnType("datetime2");
+
+ b.Property("CreatedDate")
+ .HasColumnType("datetime2");
+
+ b.Property("DeleterUserId")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("DeletionTime")
+ .HasColumnType("datetime2");
+
+ b.Property("Description")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("DispatchNumber")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("DispatchedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("EmailSent")
+ .HasColumnType("bit");
+
+ b.Property("IsDeleted")
+ .HasColumnType("bit");
+
+ b.Property("LastModificationTime")
+ .HasColumnType("datetime2");
+
+ b.Property("LastModifierUserId")
+ .HasColumnType("int");
+
+ b.Property("NTEAmount")
+ .HasColumnType("decimal(18,2)");
+
+ b.Property("PONumber")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("ReplyToAddress")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("RowVersion")
+ .IsConcurrencyToken()
+ .ValueGeneratedOnAddOrUpdate()
+ .HasColumnType("rowversion");
+
+ b.Property("ScheduledDate")
+ .HasColumnType("datetime2");
+
+ b.Property("Status")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("VendorId")
+ .HasColumnType("int");
+
+ b.Property("VerifiedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("VerifiedBy")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("WorkOrderId")
+ .HasColumnType("int");
+
+ b.Property("createdby")
+ .HasColumnType("nvarchar(max)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("VendorId");
+
+ b.HasIndex("WorkOrderId");
+
+ b.ToTable("Dispatches", (string)null);
+ });
+
+ modelBuilder.Entity("Data.SeaHavenIndustries.DispatchChecklistItem", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("CompletedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("CompletedBy")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("CreatedDate")
+ .HasColumnType("datetime2");
+
+ b.Property("DeleterUserId")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("DeletionTime")
+ .HasColumnType("datetime2");
+
+ b.Property("DispatchId")
+ .HasColumnType("int");
+
+ b.Property("IsCompleted")
+ .HasColumnType("bit");
+
+ b.Property("IsDeleted")
+ .HasColumnType("bit");
+
+ b.Property("ItemText")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("LastModificationTime")
+ .HasColumnType("datetime2");
+
+ b.Property("LastModifierUserId")
+ .HasColumnType("int");
+
+ b.Property("SortOrder")
+ .HasColumnType("int");
+
+ b.Property("WorkOrderId")
+ .HasColumnType("int");
+
+ b.Property("createdby")
+ .HasColumnType("nvarchar(max)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("DispatchId");
+
+ b.HasIndex("WorkOrderId");
+
+ b.ToTable("DispatchChecklistItems", (string)null);
+ });
+
+ modelBuilder.Entity("Data.SeaHavenIndustries.DispatchSignoff", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("CreatedDate")
+ .HasColumnType("datetime2");
+
+ b.Property("DeleterUserId")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("DeletionTime")
+ .HasColumnType("datetime2");
+
+ b.Property("DispatchId")
+ .HasColumnType("int");
+
+ b.Property("IsDeleted")
+ .HasColumnType("bit");
+
+ b.Property("LastModificationTime")
+ .HasColumnType("datetime2");
+
+ b.Property("LastModifierUserId")
+ .HasColumnType("int");
+
+ b.Property("Name")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Signature")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("SignatureMethod")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("SignedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("SignoffType")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("createdby")
+ .HasColumnType("nvarchar(max)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("DispatchId");
+
+ b.ToTable("DispatchSignoffs", (string)null);
+ });
+
+ modelBuilder.Entity("Data.SeaHavenIndustries.DispatchUpliftRequest", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("CreatedDate")
+ .HasColumnType("datetime2");
+
+ b.Property("CurrentNTE")
+ .HasColumnType("decimal(18,2)");
+
+ b.Property("DecidedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("DecidedByUserId")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("DecisionNote")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property