From 2a12444e4b787bf08b02c9e6fb79a8ce570d2e87 Mon Sep 17 00:00:00 2001 From: Jerry Phillips Date: Tue, 24 Mar 2026 09:09:00 -0400 Subject: [PATCH] feat(language): Implement Language Translation --- JobFlow.API/Controllers/ChatController.cs | 170 +- .../OrganizationClientController.cs | 12 +- JobFlow.API/Controllers/UserController.cs | 28 +- JobFlow.API/Mappings/MapsterConfig.cs | 4 + .../OrganizationBrandingMappingExtension.cs | 2 + JobFlow.API/Models/BrandingDto.cs | 1 + .../Models/DTOs/UserProfileDto.cs | 9 + .../Models/DTOs/UserProfileUpdateRequest.cs | 8 + .../Services/OrganizationBrandingService.cs | 2 +- .../ServiceInterfaces/IUserService.cs | 3 + JobFlow.Business/Services/UserService.cs | 46 + JobFlow.Domain/Models/User.cs | 1 + .../OrganizationBrandingConfiguration.cs | 22 + .../Configurations/UserConfiguration.cs | 2 + .../JobFlowDbContext.cs | 1 + ...238_AddPreferredLanguageToUser.Designer.cs | 3249 ++++++++++++++++ ...260324022238_AddPreferredLanguageToUser.cs | 29 + ...041623_AddOrganizationBranding.Designer.cs | 3313 +++++++++++++++++ .../20260324041623_AddOrganizationBranding.cs | 58 + .../JobFlowDbContextModelSnapshot.cs | 248 +- 20 files changed, 7096 insertions(+), 112 deletions(-) create mode 100644 JobFlow.Business/Models/DTOs/UserProfileDto.cs create mode 100644 JobFlow.Business/Models/DTOs/UserProfileUpdateRequest.cs create mode 100644 JobFlow.Infrastructure.Persistence/Configurations/OrganizationBrandingConfiguration.cs create mode 100644 JobFlow.Infrastructure.Persistence/Migrations/20260324022238_AddPreferredLanguageToUser.Designer.cs create mode 100644 JobFlow.Infrastructure.Persistence/Migrations/20260324022238_AddPreferredLanguageToUser.cs create mode 100644 JobFlow.Infrastructure.Persistence/Migrations/20260324041623_AddOrganizationBranding.Designer.cs create mode 100644 JobFlow.Infrastructure.Persistence/Migrations/20260324041623_AddOrganizationBranding.cs diff --git a/JobFlow.API/Controllers/ChatController.cs b/JobFlow.API/Controllers/ChatController.cs index bd3f654..a7e2112 100644 --- a/JobFlow.API/Controllers/ChatController.cs +++ b/JobFlow.API/Controllers/ChatController.cs @@ -43,13 +43,83 @@ public async Task GetConversations() if (currentUser is null) return firebaseUidResult ?? Unauthorized(); + var isOrganizationMember = await _unitOfWork.RepositoryOf() + .Query() + .AnyAsync(u => u.Id == currentUser.Id && u.OrganizationId == organizationId); + + var orgUserIds = isOrganizationMember + ? await _unitOfWork.RepositoryOf() + .Query() + .Where(u => u.OrganizationId == organizationId) + .Select(u => u.Id) + .Distinct() + .ToListAsync() + : new List(); + + var orgClientConversationIds = isOrganizationMember + ? await _unitOfWork.RepositoryOf() + .Query() + .Where(c => c.OrganizationClientId.HasValue + && c.OrganizationClient != null + && c.OrganizationClient.OrganizationId == organizationId) + .Select(c => c.Id) + .ToListAsync() + : new List(); + + var legacyClientInitiatedConversationIds = isOrganizationMember + ? await _unitOfWork.RepositoryOf() + .Query() + .Where(c => !c.OrganizationClientId.HasValue + && c.Messages.Any(m => m.ExternalSenderType == "client" + || (!m.SenderId.HasValue && !string.IsNullOrWhiteSpace(m.ExternalSenderName))) + && c.Participants.Any(p => orgUserIds.Contains(p.UserId))) + .Select(c => c.Id) + .ToListAsync() + : new List(); + var conversations = await _unitOfWork.RepositoryOf() .Query() .Include(c => c.Participants) .Include(c => c.Messages) - .Where(c => c.Participants.Any(p => p.UserId == currentUser.Id)) + .Where(c => c.Participants.Any(p => p.UserId == currentUser.Id) + || orgClientConversationIds.Contains(c.Id) + || legacyClientInitiatedConversationIds.Contains(c.Id)) .ToListAsync(); + if (isOrganizationMember) + { + var missingParticipantConversationIds = conversations + .Where(c => (c.OrganizationClientId.HasValue + || c.Messages.Any(m => m.ExternalSenderType == "client" + || (!m.SenderId.HasValue && !string.IsNullOrWhiteSpace(m.ExternalSenderName)))) + && !c.Participants.Any(p => p.UserId == currentUser.Id)) + .Select(c => c.Id) + .ToList(); + + if (missingParticipantConversationIds.Count > 0) + { + foreach (var conversationId in missingParticipantConversationIds) + { + await _unitOfWork.RepositoryOf().AddAsync(new ConversationParticipant + { + ConversationId = conversationId, + UserId = currentUser.Id + }); + } + + await _unitOfWork.SaveChangesAsync(); + + conversations = await _unitOfWork.RepositoryOf() + .Query() + .Include(c => c.Participants) + .Include(c => c.Messages) + .Where(c => c.Participants.Any(p => p.UserId == currentUser.Id) + || orgClientConversationIds.Contains(c.Id) + || legacyClientInitiatedConversationIds.Contains(c.Id)) + .ToListAsync(); + } + } + var participantIds = conversations .SelectMany(c => c.Participants) .Select(p => p.UserId) @@ -102,11 +172,8 @@ public async Task GetMessages( if (pageSize < 1) pageSize = 50; if (pageSize > 200) pageSize = 200; - var isParticipant = await _unitOfWork.RepositoryOf() - .Query() - .AnyAsync(p => p.ConversationId == conversationId && p.UserId == currentUser.Id); - - if (!isParticipant) + var canAccessConversation = await EnsureConversationAccessAsync(conversationId, currentUser.Id, organizationId); + if (!canAccessConversation) return Forbid(); var messageQuery = _unitOfWork.RepositoryOf() @@ -157,11 +224,8 @@ public async Task CreateMessage([FromBody] CreateMessageRequest r if (string.IsNullOrWhiteSpace(request.Content) && string.IsNullOrWhiteSpace(request.AttachmentUrl)) return BadRequest("Message content or attachment is required."); - var isParticipant = await _unitOfWork.RepositoryOf() - .Query() - .AnyAsync(p => p.ConversationId == request.ConversationId && p.UserId == currentUser.Id); - - if (!isParticipant) + var canAccessConversation = await EnsureConversationAccessAsync(request.ConversationId, currentUser.Id, organizationId); + if (!canAccessConversation) return Forbid(); var message = new Message @@ -199,15 +263,12 @@ public async Task CreateMessage([FromBody] CreateMessageRequest r [HttpPost("conversations/{conversationId:guid}/read")] public async Task MarkConversationRead(Guid conversationId) { - var (currentUser, _, firebaseUidResult) = await ResolveCurrentUserAsync(); + var (currentUser, organizationId, firebaseUidResult) = await ResolveCurrentUserAsync(); if (currentUser is null) return firebaseUidResult ?? Unauthorized(); - var isParticipant = await _unitOfWork.RepositoryOf() - .Query() - .AnyAsync(p => p.ConversationId == conversationId && p.UserId == currentUser.Id); - - if (!isParticipant) + var canAccessConversation = await EnsureConversationAccessAsync(conversationId, currentUser.Id, organizationId); + if (!canAccessConversation) return Forbid(); var messages = await _unitOfWork.RepositoryOf() @@ -517,6 +578,81 @@ private static (string? name, string? role, string? avatarUrl) ResolveParticipan return (userResult.Value, organizationId, null); } + private async Task EnsureConversationAccessAsync(Guid conversationId, Guid userId, Guid organizationId) + { + var participantExists = await _unitOfWork.RepositoryOf() + .Query() + .AnyAsync(p => p.ConversationId == conversationId && p.UserId == userId); + + if (participantExists) + return true; + + var conversation = await _unitOfWork.RepositoryOf() + .Query() + .FirstOrDefaultAsync(c => c.Id == conversationId); + + if (conversation is null) + return false; + + if (!conversation.OrganizationClientId.HasValue) + { + var orgUserIds = await _unitOfWork.RepositoryOf() + .Query() + .Where(u => u.OrganizationId == organizationId) + .Select(u => u.Id) + .Distinct() + .ToListAsync(); + + var isLegacyClientInitiated = await _unitOfWork.RepositoryOf() + .Query() + .AnyAsync(m => m.ConversationId == conversationId + && (m.ExternalSenderType == "client" + || (!m.SenderId.HasValue && !string.IsNullOrWhiteSpace(m.ExternalSenderName)))); + + if (!isLegacyClientInitiated) + return false; + + var hasOrgParticipant = await _unitOfWork.RepositoryOf() + .Query() + .AnyAsync(p => p.ConversationId == conversationId && orgUserIds.Contains(p.UserId)); + + if (!hasOrgParticipant) + return false; + + await _unitOfWork.RepositoryOf().AddAsync(new ConversationParticipant + { + ConversationId = conversationId, + UserId = userId + }); + await _unitOfWork.SaveChangesAsync(); + + return true; + } + + var isOrganizationMember = await _unitOfWork.RepositoryOf() + .Query() + .AnyAsync(u => u.Id == userId && u.OrganizationId == organizationId); + + if (!isOrganizationMember) + return false; + + var belongsToOrganization = await _unitOfWork.RepositoryOf() + .Query() + .AnyAsync(c => c.Id == conversation.OrganizationClientId.Value && c.OrganizationId == organizationId); + + if (!belongsToOrganization) + return false; + + await _unitOfWork.RepositoryOf().AddAsync(new ConversationParticipant + { + ConversationId = conversationId, + UserId = userId + }); + await _unitOfWork.SaveChangesAsync(); + + return true; + } + private async Task SendToClientHubAsync(Guid conversationId, Message message) { var conversation = await _unitOfWork.RepositoryOf() diff --git a/JobFlow.API/Controllers/OrganizationClientController.cs b/JobFlow.API/Controllers/OrganizationClientController.cs index 6c409be..65b1312 100644 --- a/JobFlow.API/Controllers/OrganizationClientController.cs +++ b/JobFlow.API/Controllers/OrganizationClientController.cs @@ -1,5 +1,6 @@ using JobFlow.API.Extensions; using JobFlow.API.Mappings; +using JobFlow.Business; using JobFlow.API.Models; using JobFlow.Business.Extensions; using JobFlow.Business.Models.DTOs; @@ -64,14 +65,19 @@ public async Task UpsertClient( if (organizationId == Guid.Empty) return Results.BadRequest("OrganizationId is required."); + model.Organization = null; model.OrganizationId = organizationId; var entity = _mapper.Map(model); var result = await organizationClientService.UpsertClient(entity); - return result.IsSuccess - ? Results.Ok(result) - : result.ToProblemDetails(); + if (!result.IsSuccess) + return result.ToProblemDetails(); + + var responseDto = _mapper.Map(result.Value); + responseDto.Organization = null; + + return Results.Ok(Result.Success(responseDto)); } diff --git a/JobFlow.API/Controllers/UserController.cs b/JobFlow.API/Controllers/UserController.cs index 51b8438..4e12fc6 100644 --- a/JobFlow.API/Controllers/UserController.cs +++ b/JobFlow.API/Controllers/UserController.cs @@ -1,4 +1,6 @@ -using JobFlow.Business.Extensions; +using JobFlow.API.Extensions; +using JobFlow.Business.Extensions; +using JobFlow.Business.Models.DTOs; using JobFlow.Business.Services.ServiceInterfaces; using JobFlow.Domain.Models; using Microsoft.AspNetCore.Authorization; @@ -62,4 +64,28 @@ public async Task GetByFirebaseUid(string uid) var result = await _userService.GetUserByFirebaseUid(uid); return result.IsSuccess ? Results.Ok(result.Value) : result.ToProblemDetails(); } + + [Authorize] + [HttpGet("me")] + public async Task GetMe() + { + var firebaseUid = HttpContext.GetFirebaseUid(); + if (string.IsNullOrWhiteSpace(firebaseUid)) + return Results.Unauthorized(); + + var result = await _userService.GetProfileByFirebaseUid(firebaseUid); + return result.IsSuccess ? Results.Ok(result.Value) : result.ToProblemDetails(); + } + + [Authorize] + [HttpPut("me")] + public async Task UpdateMe([FromBody] UserProfileUpdateRequest request) + { + var firebaseUid = HttpContext.GetFirebaseUid(); + if (string.IsNullOrWhiteSpace(firebaseUid)) + return Results.Unauthorized(); + + var result = await _userService.UpdateProfile(firebaseUid, request); + return result.IsSuccess ? Results.Ok(result.Value) : result.ToProblemDetails(); + } } \ No newline at end of file diff --git a/JobFlow.API/Mappings/MapsterConfig.cs b/JobFlow.API/Mappings/MapsterConfig.cs index 8073cba..e78ef9d 100644 --- a/JobFlow.API/Mappings/MapsterConfig.cs +++ b/JobFlow.API/Mappings/MapsterConfig.cs @@ -35,6 +35,10 @@ public void Register(TypeAdapterConfig config) //OrganizationClient → DTO config.NewConfig(); + //DTO → OrganizationClient + config.NewConfig() + .Ignore(dest => dest.Organization); + //Invoice → DTO config.NewConfig(); diff --git a/JobFlow.API/Mappings/OrganizationBrandingMappingExtension.cs b/JobFlow.API/Mappings/OrganizationBrandingMappingExtension.cs index 965254a..3098033 100644 --- a/JobFlow.API/Mappings/OrganizationBrandingMappingExtension.cs +++ b/JobFlow.API/Mappings/OrganizationBrandingMappingExtension.cs @@ -13,6 +13,7 @@ public static BrandingDto ToDto(OrganizationBranding entity) LogoUrl = entity.LogoUrl, PrimaryColor = entity.PrimaryColor, SecondaryColor = entity.SecondaryColor, + BusinessName = entity.BusinessName, Tagline = entity.Tagline, FooterNote = entity.FooterNote }; @@ -26,6 +27,7 @@ public static OrganizationBranding ToEntity(BrandingDto dto) LogoUrl = dto.LogoUrl, PrimaryColor = dto.PrimaryColor, SecondaryColor = dto.SecondaryColor, + BusinessName = dto.BusinessName, Tagline = dto.Tagline, FooterNote = dto.FooterNote }; diff --git a/JobFlow.API/Models/BrandingDto.cs b/JobFlow.API/Models/BrandingDto.cs index 11e9619..6596956 100644 --- a/JobFlow.API/Models/BrandingDto.cs +++ b/JobFlow.API/Models/BrandingDto.cs @@ -7,6 +7,7 @@ public class BrandingDto public string? LogoUrl { get; set; } public string? PrimaryColor { get; set; } public string? SecondaryColor { get; set; } + public string? BusinessName { get; set; } public string? Tagline { get; set; } public string? FooterNote { get; set; } } \ No newline at end of file diff --git a/JobFlow.Business/Models/DTOs/UserProfileDto.cs b/JobFlow.Business/Models/DTOs/UserProfileDto.cs new file mode 100644 index 0000000..2f3fa5d --- /dev/null +++ b/JobFlow.Business/Models/DTOs/UserProfileDto.cs @@ -0,0 +1,9 @@ +namespace JobFlow.Business.Models.DTOs; + +public class UserProfileDto +{ + public Guid Id { get; set; } + public string? Email { get; set; } + public string? PhoneNumber { get; set; } + public string? PreferredLanguage { get; set; } +} diff --git a/JobFlow.Business/Models/DTOs/UserProfileUpdateRequest.cs b/JobFlow.Business/Models/DTOs/UserProfileUpdateRequest.cs new file mode 100644 index 0000000..91e997f --- /dev/null +++ b/JobFlow.Business/Models/DTOs/UserProfileUpdateRequest.cs @@ -0,0 +1,8 @@ +namespace JobFlow.Business.Models.DTOs; + +public class UserProfileUpdateRequest +{ + public string? Email { get; set; } + public string? PhoneNumber { get; set; } + public string? PreferredLanguage { get; set; } +} diff --git a/JobFlow.Business/Services/OrganizationBrandingService.cs b/JobFlow.Business/Services/OrganizationBrandingService.cs index 8af3b2e..a6ad94f 100644 --- a/JobFlow.Business/Services/OrganizationBrandingService.cs +++ b/JobFlow.Business/Services/OrganizationBrandingService.cs @@ -32,7 +32,7 @@ public async Task> GetByOrganizationIdAsync(Guid or .FirstOrDefaultAsync(b => b.OrganizationId == organizationId); return branding is null - ? Result.Failure(Error.NotFound("", "Branding not found.")) + ? Result.Success(new OrganizationBranding { OrganizationId = organizationId }) : Result.Success(branding); } catch (Exception ex) diff --git a/JobFlow.Business/Services/ServiceInterfaces/IUserService.cs b/JobFlow.Business/Services/ServiceInterfaces/IUserService.cs index 48414cc..ecd6f22 100644 --- a/JobFlow.Business/Services/ServiceInterfaces/IUserService.cs +++ b/JobFlow.Business/Services/ServiceInterfaces/IUserService.cs @@ -1,4 +1,5 @@ using JobFlow.Domain.Models; +using JobFlow.Business.Models.DTOs; namespace JobFlow.Business.Services.ServiceInterfaces; @@ -11,4 +12,6 @@ public interface IUserService Task DeleteUser(Guid userId); Task> GetUserByEmail(string email); Task AssignRole(Guid userId, string role); + Task> GetProfileByFirebaseUid(string uid); + Task> UpdateProfile(string uid, UserProfileUpdateRequest request); } \ No newline at end of file diff --git a/JobFlow.Business/Services/UserService.cs b/JobFlow.Business/Services/UserService.cs index ae9a954..8351bd8 100644 --- a/JobFlow.Business/Services/UserService.cs +++ b/JobFlow.Business/Services/UserService.cs @@ -1,5 +1,6 @@ using JobFlow.Business.DI; using JobFlow.Business.ModelErrors; +using JobFlow.Business.Models.DTOs; using JobFlow.Business.Services.ServiceInterfaces; using JobFlow.Domain; using JobFlow.Domain.Models; @@ -122,6 +123,51 @@ public async Task> GetUserByFirebaseUid(string uid) return Result.Success(user); } + public async Task> GetProfileByFirebaseUid(string uid) + { + var user = await unitOfWork.RepositoryOf() + .Query() + .FirstOrDefaultAsync(u => u.FirebaseUid == uid); + + if (user == null) + return Result.Failure(UserErrors.UserNotFound); + + return Result.Success(ToProfileDto(user)); + } + + public async Task> UpdateProfile(string uid, UserProfileUpdateRequest request) + { + var user = await unitOfWork.RepositoryOf() + .Query() + .FirstOrDefaultAsync(u => u.FirebaseUid == uid); + + if (user == null) + return Result.Failure(UserErrors.UserNotFound); + + if (request.Email != null) + user.Email = request.Email; + if (request.PhoneNumber != null) + user.PhoneNumber = request.PhoneNumber; + if (request.PreferredLanguage != null) + user.PreferredLanguage = request.PreferredLanguage; + + unitOfWork.RepositoryOf().Update(user); + await unitOfWork.SaveChangesAsync(); + + return Result.Success(ToProfileDto(user)); + } + + private static UserProfileDto ToProfileDto(User user) + { + return new UserProfileDto + { + Id = user.Id, + Email = user.Email, + PhoneNumber = user.PhoneNumber, + PreferredLanguage = user.PreferredLanguage + }; + } + private static string ResolvePrimaryRole(User user) { diff --git a/JobFlow.Domain/Models/User.cs b/JobFlow.Domain/Models/User.cs index 9748976..e6e33a8 100644 --- a/JobFlow.Domain/Models/User.cs +++ b/JobFlow.Domain/Models/User.cs @@ -4,6 +4,7 @@ public class User : Entity { public string? Email { get; set; } public string? PhoneNumber { get; set; } + public string? PreferredLanguage { get; set; } public Guid OrganizationId { get; set; } public string? FirebaseUid { get; set; } public Guid? ClientId { get; set; } diff --git a/JobFlow.Infrastructure.Persistence/Configurations/OrganizationBrandingConfiguration.cs b/JobFlow.Infrastructure.Persistence/Configurations/OrganizationBrandingConfiguration.cs new file mode 100644 index 0000000..5b4cc81 --- /dev/null +++ b/JobFlow.Infrastructure.Persistence/Configurations/OrganizationBrandingConfiguration.cs @@ -0,0 +1,22 @@ +using JobFlow.Domain.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace JobFlow.Infrastructure.Persistence.Configurations; + +internal class OrganizationBrandingConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("OrganizationBranding"); + builder.HasKey(x => x.Id); + + builder.HasIndex(x => x.OrganizationId) + .IsUnique(); + + builder.HasOne(x => x.Organization) + .WithMany() + .HasForeignKey(x => x.OrganizationId) + .OnDelete(DeleteBehavior.Cascade); + } +} diff --git a/JobFlow.Infrastructure.Persistence/Configurations/UserConfiguration.cs b/JobFlow.Infrastructure.Persistence/Configurations/UserConfiguration.cs index 5b4e994..0c83db7 100644 --- a/JobFlow.Infrastructure.Persistence/Configurations/UserConfiguration.cs +++ b/JobFlow.Infrastructure.Persistence/Configurations/UserConfiguration.cs @@ -9,5 +9,7 @@ internal class UserConfiguration : IEntityTypeConfiguration public void Configure(EntityTypeBuilder builder) { builder.ToTable("Users"); + builder.Property(u => u.PreferredLanguage) + .HasMaxLength(10); } } \ No newline at end of file diff --git a/JobFlow.Infrastructure.Persistence/JobFlowDbContext.cs b/JobFlow.Infrastructure.Persistence/JobFlowDbContext.cs index d8b2196..1efdf76 100644 --- a/JobFlow.Infrastructure.Persistence/JobFlowDbContext.cs +++ b/JobFlow.Infrastructure.Persistence/JobFlowDbContext.cs @@ -19,6 +19,7 @@ public JobFlowDbContext(DbContextOptions options) : base(options) public DbSet JobUpdateAttachments { get; set; } public DbSet InvoiceSequences { get; set; } public DbSet Organizations { get; set; } + public DbSet OrganizationBrandings { get; set; } public DbSet OrganizationTypes { get; set; } public DbSet EmployeeRolePresets { get; set; } public DbSet EmployeeRolePresetItems { get; set; } diff --git a/JobFlow.Infrastructure.Persistence/Migrations/20260324022238_AddPreferredLanguageToUser.Designer.cs b/JobFlow.Infrastructure.Persistence/Migrations/20260324022238_AddPreferredLanguageToUser.Designer.cs new file mode 100644 index 0000000..cd272fd --- /dev/null +++ b/JobFlow.Infrastructure.Persistence/Migrations/20260324022238_AddPreferredLanguageToUser.Designer.cs @@ -0,0 +1,3249 @@ +// +using System; +using JobFlow.Infrastructure.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace JobFlow.Infrastructure.Persistence.Migrations +{ + [DbContext(typeof(JobFlowDbContext))] + [Migration("20260324022238_AddPreferredLanguageToUser")] + partial class AddPreferredLanguageToUser + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.3") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("JobFlow.Domain.Models.Assignment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ActualEnd") + .HasColumnType("datetimeoffset"); + + b.Property("ActualStart") + .HasColumnType("datetimeoffset"); + + b.Property("Address1") + .HasColumnType("nvarchar(max)"); + + b.Property("City") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("JobId") + .HasColumnType("uniqueidentifier"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("ScheduleType") + .HasColumnType("int"); + + b.Property("ScheduledEnd") + .HasColumnType("datetimeoffset"); + + b.Property("ScheduledStart") + .HasColumnType("datetimeoffset"); + + b.Property("State") + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ZipCode") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("JobId"); + + b.HasIndex("ScheduledStart"); + + b.ToTable("Assignment", (string)null); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.AssignmentAssignee", b => + { + b.Property("AssignmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("EmployeeId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("IsLead") + .HasColumnType("bit"); + + b.HasKey("AssignmentId", "EmployeeId"); + + b.HasIndex("EmployeeId") + .HasDatabaseName("IX_AssignmentAssignee_EmployeeId"); + + b.ToTable("AssignmentAssignee", (string)null); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.AssignmentHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AssignmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("ChangedAt") + .HasColumnType("datetime2"); + + b.Property("ChangedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("EventType") + .HasColumnType("int"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("NewScheduledEnd") + .HasColumnType("datetime2"); + + b.Property("NewScheduledStart") + .HasColumnType("datetime2"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("OldScheduledEnd") + .HasColumnType("datetime2"); + + b.Property("OldScheduledStart") + .HasColumnType("datetime2"); + + b.Property("Reason") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("AssignmentId") + .HasDatabaseName("IX_AssignmentHistory_AssignmentId"); + + b.ToTable("AssignmentHistory", (string)null); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.AssignmentOrder", b => + { + b.Property("AssignmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("OrderId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.HasKey("AssignmentId", "OrderId"); + + b.HasIndex("AssignmentId"); + + b.HasIndex("OrderId"); + + b.ToTable("AssignmentOrder", (string)null); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.Conversation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("OrganizationClientId") + .HasColumnType("uniqueidentifier"); + + b.Property("Title") + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationClientId"); + + b.ToTable("Conversation", "messaging"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.ConversationParticipant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConversationId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("ConversationId", "UserId") + .IsUnique(); + + b.ToTable("ConversationParticipant", "messaging"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.CustomerPaymentProfile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("DefaultPaymentMethodId") + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDelinquent") + .HasColumnType("bit"); + + b.Property("OrganizationClientId") + .HasColumnType("uniqueidentifier"); + + b.Property("OrganizationId") + .HasColumnType("uniqueidentifier"); + + b.Property("OwnerId") + .HasColumnType("uniqueidentifier"); + + b.Property("OwnerType") + .HasColumnType("int"); + + b.Property("Provider") + .HasColumnType("int"); + + b.Property("ProviderCustomerId") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationClientId"); + + b.HasIndex("OrganizationId"); + + b.ToTable("CustomerPaymentProfile", "payment"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.Employee", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("Email") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("HireDate") + .HasColumnType("datetime2"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("JobTitle") + .HasColumnType("nvarchar(max)"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("OrganizationId") + .HasColumnType("uniqueidentifier"); + + b.Property("PhoneNumber") + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("ProfilePictureUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.Property("TerminationDate") + .HasColumnType("datetime2"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("RoleId"); + + b.HasIndex("UserId"); + + b.ToTable("Employees", (string)null); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.EmployeeInvite", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccessCount") + .HasColumnType("int"); + + b.Property("AccessIpAddress") + .HasColumnType("nvarchar(max)"); + + b.Property("AccessedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("ExpiresAt") + .HasColumnType("datetime2"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b.Property("InviteToken") + .HasMaxLength(128) + .HasColumnType("uniqueidentifier"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b.Property("OrganizationId") + .HasColumnType("uniqueidentifier"); + + b.Property("PhoneNumber") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.Property("ShortCode") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("Status") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(1); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("InviteToken") + .IsUnique(); + + b.HasIndex("OrganizationId"); + + b.HasIndex("RoleId"); + + b.HasIndex("ShortCode") + .IsUnique() + .HasFilter("[ShortCode] IS NOT NULL"); + + b.HasIndex("OrganizationId", "Email") + .IsUnique() + .HasFilter("[Status] = 1"); + + b.HasIndex("Status", "ExpiresAt"); + + b.ToTable("EmployeeInvites", (string)null); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.EmployeeRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Description") + .HasMaxLength(240) + .HasColumnType("nvarchar(240)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("OrganizationId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("EmployeeRoles", (string)null); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.EmployeeRolePreset", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasMaxLength(240) + .HasColumnType("nvarchar(240)"); + + b.Property("IndustryKey") + .HasMaxLength(80) + .HasColumnType("nvarchar(80)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsSystem") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("nvarchar(120)"); + + b.Property("OrganizationId") + .HasColumnType("uniqueidentifier"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("EmployeeRolePresets", (string)null); + + b.HasData( + new + { + Id = new Guid("1a2b3c4d-1111-1111-1111-111111111111"), + CreatedAt = new DateTime(2026, 3, 23, 0, 0, 0, 0, DateTimeKind.Utc), + Description = "Default roles for field service teams.", + IndustryKey = "home-services", + IsActive = true, + IsSystem = true, + Name = "Home services" + }, + new + { + Id = new Guid("1a2b3c4d-2222-2222-2222-222222222222"), + CreatedAt = new DateTime(2026, 3, 23, 0, 0, 0, 0, DateTimeKind.Utc), + Description = "Default roles for creative studios.", + IndustryKey = "creative", + IsActive = true, + IsSystem = true, + Name = "Creative" + }, + new + { + Id = new Guid("1a2b3c4d-3333-3333-3333-333333333333"), + CreatedAt = new DateTime(2026, 3, 23, 0, 0, 0, 0, DateTimeKind.Utc), + Description = "Default roles for consulting teams.", + IndustryKey = "consulting", + IsActive = true, + IsSystem = true, + Name = "Consulting" + }, + new + { + Id = new Guid("1a2b3c4d-4444-4444-4444-444444444444"), + CreatedAt = new DateTime(2026, 3, 23, 0, 0, 0, 0, DateTimeKind.Utc), + Description = "Default roles for repair shops.", + IndustryKey = "tech-repair", + IsActive = true, + IsSystem = true, + Name = "Tech repair" + }); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.EmployeeRolePresetItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasMaxLength(240) + .HasColumnType("nvarchar(240)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("nvarchar(120)"); + + b.Property("PresetId") + .HasColumnType("uniqueidentifier"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("PresetId"); + + b.ToTable("EmployeeRolePresetItems", (string)null); + + b.HasData( + new + { + Id = new Guid("2a2b3c4d-1111-1111-1111-111111111111"), + CreatedAt = new DateTime(2026, 3, 23, 0, 0, 0, 0, DateTimeKind.Utc), + Description = "Field technician for on-site work.", + IsActive = true, + Name = "Technician", + PresetId = new Guid("1a2b3c4d-1111-1111-1111-111111111111"), + SortOrder = 1 + }, + new + { + Id = new Guid("2a2b3c4d-1111-1111-1111-111111111112"), + CreatedAt = new DateTime(2026, 3, 23, 0, 0, 0, 0, DateTimeKind.Utc), + Description = "Lead for quality checks and approvals.", + IsActive = true, + Name = "Supervisor", + PresetId = new Guid("1a2b3c4d-1111-1111-1111-111111111111"), + SortOrder = 2 + }, + new + { + Id = new Guid("2a2b3c4d-1111-1111-1111-111111111113"), + CreatedAt = new DateTime(2026, 3, 23, 0, 0, 0, 0, DateTimeKind.Utc), + Description = "Routes schedules and job assignments.", + IsActive = true, + Name = "Dispatcher", + PresetId = new Guid("1a2b3c4d-1111-1111-1111-111111111111"), + SortOrder = 3 + }, + new + { + Id = new Guid("2a2b3c4d-1111-1111-1111-111111111114"), + CreatedAt = new DateTime(2026, 3, 23, 0, 0, 0, 0, DateTimeKind.Utc), + Description = "Back-office support and billing.", + IsActive = true, + Name = "Admin", + PresetId = new Guid("1a2b3c4d-1111-1111-1111-111111111111"), + SortOrder = 4 + }, + new + { + Id = new Guid("2a2b3c4d-2222-2222-2222-222222222221"), + CreatedAt = new DateTime(2026, 3, 23, 0, 0, 0, 0, DateTimeKind.Utc), + Description = "Primary creator and deliverable owner.", + IsActive = true, + Name = "Designer", + PresetId = new Guid("1a2b3c4d-2222-2222-2222-222222222222"), + SortOrder = 1 + }, + new + { + Id = new Guid("2a2b3c4d-2222-2222-2222-222222222222"), + CreatedAt = new DateTime(2026, 3, 23, 0, 0, 0, 0, DateTimeKind.Utc), + Description = "Owns timelines, approvals, and client comms.", + IsActive = true, + Name = "Producer", + PresetId = new Guid("1a2b3c4d-2222-2222-2222-222222222222"), + SortOrder = 2 + }, + new + { + Id = new Guid("2a2b3c4d-2222-2222-2222-222222222223"), + CreatedAt = new DateTime(2026, 3, 23, 0, 0, 0, 0, DateTimeKind.Utc), + Description = "Schedules tasks and supports delivery.", + IsActive = true, + Name = "Coordinator", + PresetId = new Guid("1a2b3c4d-2222-2222-2222-222222222222"), + SortOrder = 3 + }, + new + { + Id = new Guid("2a2b3c4d-2222-2222-2222-222222222224"), + CreatedAt = new DateTime(2026, 3, 23, 0, 0, 0, 0, DateTimeKind.Utc), + Description = "Operations and billing support.", + IsActive = true, + Name = "Admin", + PresetId = new Guid("1a2b3c4d-2222-2222-2222-222222222222"), + SortOrder = 4 + }, + new + { + Id = new Guid("2a2b3c4d-3333-3333-3333-333333333331"), + CreatedAt = new DateTime(2026, 3, 23, 0, 0, 0, 0, DateTimeKind.Utc), + Description = "Client-facing delivery specialist.", + IsActive = true, + Name = "Consultant", + PresetId = new Guid("1a2b3c4d-3333-3333-3333-333333333333"), + SortOrder = 1 + }, + new + { + Id = new Guid("2a2b3c4d-3333-3333-3333-333333333332"), + CreatedAt = new DateTime(2026, 3, 23, 0, 0, 0, 0, DateTimeKind.Utc), + Description = "Owns engagement delivery and quality.", + IsActive = true, + Name = "Lead", + PresetId = new Guid("1a2b3c4d-3333-3333-3333-333333333333"), + SortOrder = 2 + }, + new + { + Id = new Guid("2a2b3c4d-3333-3333-3333-333333333333"), + CreatedAt = new DateTime(2026, 3, 23, 0, 0, 0, 0, DateTimeKind.Utc), + Description = "Plans meetings and follow-ups.", + IsActive = true, + Name = "Coordinator", + PresetId = new Guid("1a2b3c4d-3333-3333-3333-333333333333"), + SortOrder = 3 + }, + new + { + Id = new Guid("2a2b3c4d-3333-3333-3333-333333333334"), + CreatedAt = new DateTime(2026, 3, 23, 0, 0, 0, 0, DateTimeKind.Utc), + Description = "Back-office support and billing.", + IsActive = true, + Name = "Admin", + PresetId = new Guid("1a2b3c4d-3333-3333-3333-333333333333"), + SortOrder = 4 + }, + new + { + Id = new Guid("2a2b3c4d-4444-4444-4444-444444444441"), + CreatedAt = new DateTime(2026, 3, 23, 0, 0, 0, 0, DateTimeKind.Utc), + Description = "Executes diagnostics and repairs.", + IsActive = true, + Name = "Repair Tech", + PresetId = new Guid("1a2b3c4d-4444-4444-4444-444444444444"), + SortOrder = 1 + }, + new + { + Id = new Guid("2a2b3c4d-4444-4444-4444-444444444442"), + CreatedAt = new DateTime(2026, 3, 23, 0, 0, 0, 0, DateTimeKind.Utc), + Description = "Final testing and release approvals.", + IsActive = true, + Name = "QA", + PresetId = new Guid("1a2b3c4d-4444-4444-4444-444444444444"), + SortOrder = 2 + }, + new + { + Id = new Guid("2a2b3c4d-4444-4444-4444-444444444443"), + CreatedAt = new DateTime(2026, 3, 23, 0, 0, 0, 0, DateTimeKind.Utc), + Description = "Client intake and status updates.", + IsActive = true, + Name = "Service Advisor", + PresetId = new Guid("1a2b3c4d-4444-4444-4444-444444444444"), + SortOrder = 3 + }, + new + { + Id = new Guid("2a2b3c4d-4444-4444-4444-444444444444"), + CreatedAt = new DateTime(2026, 3, 23, 0, 0, 0, 0, DateTimeKind.Utc), + Description = "Operations and billing support.", + IsActive = true, + Name = "Admin", + PresetId = new Guid("1a2b3c4d-4444-4444-4444-444444444444"), + SortOrder = 4 + }); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.Estimate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetimeoffset"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("EstimateNumber") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("OrganizationClientId") + .HasColumnType("uniqueidentifier"); + + b.Property("OrganizationId") + .HasColumnType("uniqueidentifier"); + + b.Property("PublicToken") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("PublicTokenExpiresAt") + .HasColumnType("datetimeoffset"); + + b.Property("SentAt") + .HasColumnType("datetimeoffset"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Subtotal") + .HasColumnType("decimal(18,2)"); + + b.Property("TaxTotal") + .HasColumnType("decimal(18,2)"); + + b.Property("Title") + .HasColumnType("nvarchar(max)"); + + b.Property("Total") + .HasColumnType("decimal(18,2)"); + + b.Property("UpdatedAt") + .HasColumnType("datetimeoffset"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationClientId"); + + b.HasIndex("PublicToken") + .IsUnique(); + + b.HasIndex("OrganizationId", "EstimateNumber") + .IsUnique(); + + b.ToTable("Estimates", (string)null); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.EstimateLineItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("EstimateId") + .HasColumnType("uniqueidentifier"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Quantity") + .HasColumnType("decimal(18,2)"); + + b.Property("Total") + .HasColumnType("decimal(18,2)"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("EstimateId"); + + b.ToTable("EstimateLineItems", (string)null); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.EstimateRevisionAttachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ContentType") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("EstimateRevisionRequestId") + .HasColumnType("uniqueidentifier"); + + b.Property("FileData") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(260) + .HasColumnType("nvarchar(260)"); + + b.Property("FileSizeBytes") + .HasColumnType("bigint"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("EstimateRevisionRequestId"); + + b.ToTable("EstimateRevisionAttachments", (string)null); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.EstimateRevisionRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("EstimateId") + .HasColumnType("uniqueidentifier"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("OrganizationClientId") + .HasColumnType("uniqueidentifier"); + + b.Property("OrganizationId") + .HasColumnType("uniqueidentifier"); + + b.Property("OrganizationResponseMessage") + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("RequestMessage") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("RequestedAt") + .HasColumnType("datetimeoffset"); + + b.Property("ResolvedAt") + .HasColumnType("datetimeoffset"); + + b.Property("ReviewedAt") + .HasColumnType("datetimeoffset"); + + b.Property("RevisionNumber") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationClientId"); + + b.HasIndex("EstimateId", "RevisionNumber") + .IsUnique(); + + b.ToTable("EstimateRevisionRequests", (string)null); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.InventoryItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Category") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CostPerUnit") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("OrganizationId") + .HasColumnType("uniqueidentifier"); + + b.Property("QuantityInStock") + .HasColumnType("int"); + + b.Property("Unit") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId", "Name") + .IsUnique(); + + b.ToTable("InventoryItems", (string)null); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.Invoice", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AmountPaid") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("DueDate") + .HasColumnType("datetime2"); + + b.Property("ExternalPaymentId") + .HasColumnType("nvarchar(max)"); + + b.Property("InvoiceDate") + .HasColumnType("datetime2"); + + b.Property("InvoiceNumber") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("JobId") + .HasColumnType("uniqueidentifier"); + + b.Property("OrderId") + .HasColumnType("uniqueidentifier"); + + b.Property("OrganizationClientId") + .HasColumnType("uniqueidentifier"); + + b.Property("OrganizationId") + .HasColumnType("uniqueidentifier"); + + b.Property("PaidAt") + .HasColumnType("datetimeoffset"); + + b.Property("PaymentProvider") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("TotalAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("JobId"); + + b.HasIndex("OrderId"); + + b.HasIndex("OrganizationClientId"); + + b.ToTable("Invoice"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.InvoiceLineItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("InvoiceId") + .HasColumnType("uniqueidentifier"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("InvoiceId"); + + b.ToTable("InvoiceLineItem", (string)null); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.InvoiceSequence", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("LastSequence") + .HasColumnType("int"); + + b.Property("OrganizationId") + .HasColumnType("uniqueidentifier"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Year") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("InvoiceSequence", (string)null); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Comments") + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("InvoicingWorkflow") + .HasColumnType("int"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("Latitude") + .HasColumnType("float"); + + b.Property("LifecycleStatus") + .HasColumnType("int"); + + b.Property("Longitude") + .HasColumnType("float"); + + b.Property("OrganizationClientId") + .HasColumnType("uniqueidentifier"); + + b.Property("Title") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationClientId"); + + b.ToTable("Job", (string)null); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.JobRecurrence", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DaysOfWeekMask") + .HasColumnType("int"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("Duration") + .HasColumnType("time"); + + b.Property("EndDate") + .HasColumnType("datetime2"); + + b.Property("Frequency") + .HasColumnType("int"); + + b.Property("GenerateDaysAhead") + .HasColumnType("int"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("JobId") + .HasColumnType("uniqueidentifier"); + + b.Property("ScheduleType") + .HasColumnType("int"); + + b.Property("StartDate") + .HasColumnType("datetime2"); + + b.Property("StartTime") + .HasColumnType("time"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("JobId", "IsActive") + .HasDatabaseName("IX_JobRecurrence_JobId_IsActive"); + + b.ToTable("JobRecurrence", (string)null); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.JobTracking", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("EmployeeId") + .HasColumnType("uniqueidentifier"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("JobId") + .HasColumnType("uniqueidentifier"); + + b.Property("Latitude") + .HasColumnType("decimal(9,6)"); + + b.Property("Longitude") + .HasColumnType("decimal(9,6)"); + + b.Property("RecordedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("GETUTCDATE()"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("EmployeeId"); + + b.HasIndex("JobId"); + + b.ToTable("JobTracking"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.JobUpdate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("JobId") + .HasColumnType("uniqueidentifier"); + + b.Property("Message") + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("OccurredAt") + .HasColumnType("datetimeoffset"); + + b.Property("OrganizationClientId") + .HasColumnType("uniqueidentifier"); + + b.Property("OrganizationId") + .HasColumnType("uniqueidentifier"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("JobId", "OccurredAt"); + + b.ToTable("JobUpdates", (string)null); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.JobUpdateAttachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ContentType") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("FileData") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(260) + .HasColumnType("nvarchar(260)"); + + b.Property("FileSizeBytes") + .HasColumnType("bigint"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("JobUpdateId") + .HasColumnType("uniqueidentifier"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("JobUpdateId"); + + b.ToTable("JobUpdateAttachments", (string)null); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.Message", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AttachmentUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("Content") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ConversationId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("ExternalSenderName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ExternalSenderPhone") + .HasMaxLength(32) + .HasColumnType("nvarchar(32)"); + + b.Property("ExternalSenderType") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsRead") + .HasColumnType("bit"); + + b.Property("SenderId") + .HasColumnType("uniqueidentifier"); + + b.Property("SentAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ConversationId"); + + b.HasIndex("SenderId"); + + b.ToTable("Message", "messaging"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("Notes") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetime2"); + + b.Property("OrganizationClientId") + .HasColumnType("uniqueidentifier"); + + b.Property("Status") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("TotalAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationClientId"); + + b.ToTable("Order", "payment"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.Organization", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Address1") + .HasColumnType("nvarchar(max)"); + + b.Property("Address2") + .HasColumnType("nvarchar(max)"); + + b.Property("City") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("DefaultTaxRate") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("EmailAddress") + .HasColumnType("nvarchar(max)"); + + b.Property("EnableTax") + .HasColumnType("bit"); + + b.Property("HasFreeAccount") + .HasColumnType("bit"); + + b.Property("IndustryKey") + .HasMaxLength(80) + .HasColumnType("nvarchar(80)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsStripeConnected") + .HasColumnType("bit"); + + b.Property("OnBoardingComplete") + .HasColumnType("bit"); + + b.Property("OnboardingPresetAppliedAt") + .HasColumnType("datetimeoffset"); + + b.Property("OnboardingPresetKey") + .HasColumnType("nvarchar(max)"); + + b.Property("OnboardingTrack") + .HasColumnType("nvarchar(max)"); + + b.Property("OnboardingTrackSelectedAt") + .HasColumnType("datetimeoffset"); + + b.Property("OrganizationName") + .HasColumnType("nvarchar(max)"); + + b.Property("OrganizationTypeId") + .HasColumnType("uniqueidentifier"); + + b.Property("PaymentProvider") + .HasColumnType("int"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("State") + .HasColumnType("nvarchar(max)"); + + b.Property("StripeConnectAccountId") + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ZipCode") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationTypeId"); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.OrganizationClient", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Address1") + .HasColumnType("nvarchar(max)"); + + b.Property("Address2") + .HasColumnType("nvarchar(max)"); + + b.Property("City") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("EmailAddress") + .HasColumnType("nvarchar(max)"); + + b.Property("FirstName") + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("LastName") + .HasColumnType("nvarchar(max)"); + + b.Property("OrganizationId") + .HasColumnType("uniqueidentifier"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("State") + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ZipCode") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationClient", (string)null); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.OrganizationClientPortalSession", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetimeoffset"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("EmailAddress") + .IsRequired() + .HasMaxLength(320) + .HasColumnType("nvarchar(320)"); + + b.Property("ExpiresAt") + .HasColumnType("datetimeoffset"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("OrganizationClientId") + .HasColumnType("uniqueidentifier"); + + b.Property("OrganizationId") + .HasColumnType("uniqueidentifier"); + + b.Property("RedeemedAt") + .HasColumnType("datetimeoffset"); + + b.Property("TokenHash") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationClientId"); + + b.HasIndex("TokenHash") + .IsUnique(); + + b.ToTable("OrganizationClientPortalSession"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.OrganizationInvoicingSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("DefaultWorkflow") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("OrganizationId") + .HasColumnType("uniqueidentifier"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .IsUnique(); + + b.ToTable("OrganizationInvoicingSettings"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.OrganizationOnboardingStep", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetimeoffset"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsCompleted") + .HasColumnType("bit"); + + b.Property("OrganizationId") + .HasColumnType("uniqueidentifier"); + + b.Property("StepName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId", "StepName") + .IsUnique(); + + b.ToTable("OrganizationOnboardingSteps", (string)null); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.OrganizationScheduleSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AutoNotifyReschedule") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("DefaultWindowMinutes") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(120); + + b.Property("EnforceTravelBuffer") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("OrganizationId") + .HasColumnType("uniqueidentifier"); + + b.Property("TravelBufferMinutes") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(20); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .IsUnique(); + + b.ToTable("OrganizationScheduleSettings"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.OrganizationService", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("OrganizationId") + .HasColumnType("uniqueidentifier"); + + b.Property("ServiceName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationService", (string)null); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.OrganizationType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("TypeName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("OrganizationType", (string)null); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.OrganizationWorkflowStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Category") + .IsRequired() + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("Label") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("nvarchar(120)"); + + b.Property("OrganizationId") + .HasColumnType("uniqueidentifier"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("StatusKey") + .IsRequired() + .HasMaxLength(80) + .HasColumnType("nvarchar(80)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId", "Category", "StatusKey") + .IsUnique(); + + b.ToTable("OrganizationWorkflowStatus"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.PaymentHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AmountPaid") + .HasColumnType("bigint"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Currency") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CustomerId") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("EntityId") + .HasColumnType("uniqueidentifier"); + + b.Property("EntityType") + .HasColumnType("int"); + + b.Property("EventType") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("InvoiceId") + .HasColumnType("uniqueidentifier"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("PaidAt") + .HasColumnType("datetime2"); + + b.Property("PaymentProvider") + .HasColumnType("int"); + + b.Property("RawEventJson") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("StripeInvoiceId") + .HasColumnType("nvarchar(max)"); + + b.Property("StripePaymentIntentId") + .HasColumnType("nvarchar(max)"); + + b.Property("SubscriptionId") + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("InvoiceId"); + + b.ToTable("PaymentHistory"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.PriceBookCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("OrganizationId") + .HasColumnType("uniqueidentifier"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId", "Name") + .IsUnique(); + + b.ToTable("PriceBookCategories", (string)null); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.PriceBookItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CategoryId") + .HasColumnType("uniqueidentifier"); + + b.Property("Cost") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("InventoryItemId") + .HasColumnType("uniqueidentifier"); + + b.Property("InventoryUnitsPerSale") + .ValueGeneratedOnAdd() + .HasColumnType("decimal(18,4)") + .HasDefaultValue(1.0m); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsTaxable") + .HasColumnType("bit"); + + b.Property("ItemType") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("OrganizationId") + .HasColumnType("uniqueidentifier"); + + b.Property("PartNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("PricePerUnit") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("Unit") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.HasIndex("InventoryItemId"); + + b.HasIndex("OrganizationId", "Name") + .IsUnique(); + + b.ToTable("PriceBookItems", (string)null); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.SubscriptionRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CanceledAt") + .HasColumnType("datetime2"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("PaymentProfileId") + .HasColumnType("uniqueidentifier"); + + b.Property("PlanName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Provider") + .HasColumnType("int"); + + b.Property("ProviderPriceId") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("ProviderSubscriptionId") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("StartDate") + .HasColumnType("datetime2"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("nvarchar(64)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("PaymentProfileId"); + + b.ToTable("SubscriptionRecord", "payment"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.SupportHubInvite", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(12) + .HasColumnType("nvarchar(12)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("ExpiresAt") + .HasColumnType("datetimeoffset"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("RedeemedAt") + .HasColumnType("datetimeoffset"); + + b.Property("RedeemedByUid") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("Role") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.ToTable("SupportHubInvites"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.SupportHubSession", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AgentName") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("nvarchar(120)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("EndedAt") + .HasColumnType("datetimeoffset"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("OrganizationId") + .HasColumnType("uniqueidentifier"); + + b.Property("StartedAt") + .HasColumnType("datetimeoffset"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SupportHubSessions"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.SupportHubTicket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("LastActivityAt") + .HasColumnType("datetimeoffset"); + + b.Property("OrganizationId") + .HasColumnType("uniqueidentifier"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Summary") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(160) + .HasColumnType("nvarchar(160)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SupportHubTickets"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.SystemRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier") + .HasDefaultValueSql("NEWID()"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Roles", (string)null); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClientId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("Email") + .HasColumnType("nvarchar(max)"); + + b.Property("FirebaseUid") + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("OrganizationId") + .HasColumnType("uniqueidentifier"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PreferredLanguage") + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Users", (string)null); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.UserRole", b => + { + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("UserRoles", (string)null); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.Assignment", b => + { + b.HasOne("JobFlow.Domain.Models.Job", "Job") + .WithMany("Assignments") + .HasForeignKey("JobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Job"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.AssignmentAssignee", b => + { + b.HasOne("JobFlow.Domain.Models.Assignment", "Assignment") + .WithMany("AssignmentAssignees") + .HasForeignKey("AssignmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("JobFlow.Domain.Models.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Assignment"); + + b.Navigation("Employee"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.AssignmentHistory", b => + { + b.HasOne("JobFlow.Domain.Models.Assignment", "Assignment") + .WithMany() + .HasForeignKey("AssignmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Assignment"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.AssignmentOrder", b => + { + b.HasOne("JobFlow.Domain.Models.Assignment", "Assignment") + .WithMany("AssignmentOrders") + .HasForeignKey("AssignmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("JobFlow.Domain.Models.Order", "Order") + .WithMany("AssignmentOrders") + .HasForeignKey("OrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Assignment"); + + b.Navigation("Order"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.Conversation", b => + { + b.HasOne("JobFlow.Domain.Models.OrganizationClient", "OrganizationClient") + .WithMany() + .HasForeignKey("OrganizationClientId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("OrganizationClient"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.ConversationParticipant", b => + { + b.HasOne("JobFlow.Domain.Models.Conversation", "Conversation") + .WithMany("Participants") + .HasForeignKey("ConversationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("JobFlow.Domain.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Conversation"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.CustomerPaymentProfile", b => + { + b.HasOne("JobFlow.Domain.Models.OrganizationClient", null) + .WithMany("PaymentProfiles") + .HasForeignKey("OrganizationClientId"); + + b.HasOne("JobFlow.Domain.Models.Organization", null) + .WithMany("PaymentProfiles") + .HasForeignKey("OrganizationId"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.Employee", b => + { + b.HasOne("JobFlow.Domain.Models.Organization", "Organization") + .WithMany("Employees") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("JobFlow.Domain.Models.EmployeeRole", "Role") + .WithMany("Employees") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("JobFlow.Domain.Models.User", "User") + .WithMany("Employees") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Organization"); + + b.Navigation("Role"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.EmployeeInvite", b => + { + b.HasOne("JobFlow.Domain.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("JobFlow.Domain.Models.EmployeeRole", "Role") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.EmployeeRole", b => + { + b.HasOne("JobFlow.Domain.Models.Organization", "Organization") + .WithMany("EmployeeRoles") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.EmployeeRolePreset", b => + { + b.HasOne("JobFlow.Domain.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.EmployeeRolePresetItem", b => + { + b.HasOne("JobFlow.Domain.Models.EmployeeRolePreset", "Preset") + .WithMany("Items") + .HasForeignKey("PresetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Preset"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.Estimate", b => + { + b.HasOne("JobFlow.Domain.Models.OrganizationClient", "OrganizationClient") + .WithMany() + .HasForeignKey("OrganizationClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("OrganizationClient"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.EstimateLineItem", b => + { + b.HasOne("JobFlow.Domain.Models.Estimate", "Estimate") + .WithMany("LineItems") + .HasForeignKey("EstimateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Estimate"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.EstimateRevisionAttachment", b => + { + b.HasOne("JobFlow.Domain.Models.EstimateRevisionRequest", "RevisionRequest") + .WithMany("Attachments") + .HasForeignKey("EstimateRevisionRequestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RevisionRequest"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.EstimateRevisionRequest", b => + { + b.HasOne("JobFlow.Domain.Models.Estimate", "Estimate") + .WithMany("RevisionRequests") + .HasForeignKey("EstimateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("JobFlow.Domain.Models.OrganizationClient", "OrganizationClient") + .WithMany() + .HasForeignKey("OrganizationClientId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Estimate"); + + b.Navigation("OrganizationClient"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.Invoice", b => + { + b.HasOne("JobFlow.Domain.Models.Job", "Job") + .WithMany() + .HasForeignKey("JobId"); + + b.HasOne("JobFlow.Domain.Models.Order", "Order") + .WithMany("Invoices") + .HasForeignKey("OrderId"); + + b.HasOne("JobFlow.Domain.Models.OrganizationClient", "OrganizationClient") + .WithMany() + .HasForeignKey("OrganizationClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Job"); + + b.Navigation("Order"); + + b.Navigation("OrganizationClient"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.InvoiceLineItem", b => + { + b.HasOne("JobFlow.Domain.Models.Invoice", "Invoice") + .WithMany("LineItems") + .HasForeignKey("InvoiceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Invoice"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.Job", b => + { + b.HasOne("JobFlow.Domain.Models.OrganizationClient", "OrganizationClient") + .WithMany("Jobs") + .HasForeignKey("OrganizationClientId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("OrganizationClient"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.JobRecurrence", b => + { + b.HasOne("JobFlow.Domain.Models.Job", "Job") + .WithMany() + .HasForeignKey("JobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Job"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.JobTracking", b => + { + b.HasOne("JobFlow.Domain.Models.User", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("JobFlow.Domain.Models.Job", "Job") + .WithMany("JobTrackings") + .HasForeignKey("JobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Employee"); + + b.Navigation("Job"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.JobUpdate", b => + { + b.HasOne("JobFlow.Domain.Models.Job", "Job") + .WithMany("JobUpdates") + .HasForeignKey("JobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Job"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.JobUpdateAttachment", b => + { + b.HasOne("JobFlow.Domain.Models.JobUpdate", "JobUpdate") + .WithMany("Attachments") + .HasForeignKey("JobUpdateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("JobUpdate"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.Message", b => + { + b.HasOne("JobFlow.Domain.Models.Conversation", "Conversation") + .WithMany("Messages") + .HasForeignKey("ConversationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("JobFlow.Domain.Models.User", "Sender") + .WithMany() + .HasForeignKey("SenderId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Conversation"); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.Order", b => + { + b.HasOne("JobFlow.Domain.Models.OrganizationClient", "OrganizationClient") + .WithMany() + .HasForeignKey("OrganizationClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("OrganizationClient"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.Organization", b => + { + b.HasOne("JobFlow.Domain.Models.OrganizationType", "OrganizationType") + .WithMany() + .HasForeignKey("OrganizationTypeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("OrganizationType"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.OrganizationClient", b => + { + b.HasOne("JobFlow.Domain.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.OrganizationClientPortalSession", b => + { + b.HasOne("JobFlow.Domain.Models.OrganizationClient", "OrganizationClient") + .WithMany() + .HasForeignKey("OrganizationClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("OrganizationClient"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.OrganizationOnboardingStep", b => + { + b.HasOne("JobFlow.Domain.Models.Organization", "Organization") + .WithMany("OnboardingSteps") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.OrganizationService", b => + { + b.HasOne("JobFlow.Domain.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.PaymentHistory", b => + { + b.HasOne("JobFlow.Domain.Models.Invoice", "Invoice") + .WithMany("Payments") + .HasForeignKey("InvoiceId"); + + b.Navigation("Invoice"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.PriceBookItem", b => + { + b.HasOne("JobFlow.Domain.Models.PriceBookCategory", "Category") + .WithMany("Items") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("JobFlow.Domain.Models.InventoryItem", "InventoryItem") + .WithMany() + .HasForeignKey("InventoryItemId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Category"); + + b.Navigation("InventoryItem"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.SubscriptionRecord", b => + { + b.HasOne("JobFlow.Domain.Models.CustomerPaymentProfile", "PaymentProfile") + .WithMany() + .HasForeignKey("PaymentProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PaymentProfile"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.SupportHubSession", b => + { + b.HasOne("JobFlow.Domain.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.SupportHubTicket", b => + { + b.HasOne("JobFlow.Domain.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.User", b => + { + b.HasOne("JobFlow.Domain.Models.OrganizationClient", "Client") + .WithMany() + .HasForeignKey("ClientId"); + + b.HasOne("JobFlow.Domain.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.UserRole", b => + { + b.HasOne("JobFlow.Domain.Models.SystemRole", "Role") + .WithMany("UserRoles") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("JobFlow.Domain.Models.User", "User") + .WithMany("UserRoles") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.Assignment", b => + { + b.Navigation("AssignmentAssignees"); + + b.Navigation("AssignmentOrders"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.Conversation", b => + { + b.Navigation("Messages"); + + b.Navigation("Participants"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.EmployeeRole", b => + { + b.Navigation("Employees"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.EmployeeRolePreset", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.Estimate", b => + { + b.Navigation("LineItems"); + + b.Navigation("RevisionRequests"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.EstimateRevisionRequest", b => + { + b.Navigation("Attachments"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.Invoice", b => + { + b.Navigation("LineItems"); + + b.Navigation("Payments"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.Job", b => + { + b.Navigation("Assignments"); + + b.Navigation("JobTrackings"); + + b.Navigation("JobUpdates"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.JobUpdate", b => + { + b.Navigation("Attachments"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.Order", b => + { + b.Navigation("AssignmentOrders"); + + b.Navigation("Invoices"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.Organization", b => + { + b.Navigation("EmployeeRoles"); + + b.Navigation("Employees"); + + b.Navigation("OnboardingSteps"); + + b.Navigation("PaymentProfiles"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.OrganizationClient", b => + { + b.Navigation("Jobs"); + + b.Navigation("PaymentProfiles"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.PriceBookCategory", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.SystemRole", b => + { + b.Navigation("UserRoles"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.User", b => + { + b.Navigation("Employees"); + + b.Navigation("UserRoles"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/JobFlow.Infrastructure.Persistence/Migrations/20260324022238_AddPreferredLanguageToUser.cs b/JobFlow.Infrastructure.Persistence/Migrations/20260324022238_AddPreferredLanguageToUser.cs new file mode 100644 index 0000000..71a5593 --- /dev/null +++ b/JobFlow.Infrastructure.Persistence/Migrations/20260324022238_AddPreferredLanguageToUser.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace JobFlow.Infrastructure.Persistence.Migrations +{ + /// + public partial class AddPreferredLanguageToUser : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "PreferredLanguage", + table: "Users", + type: "nvarchar(10)", + maxLength: 10, + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "PreferredLanguage", + table: "Users"); + } + } +} diff --git a/JobFlow.Infrastructure.Persistence/Migrations/20260324041623_AddOrganizationBranding.Designer.cs b/JobFlow.Infrastructure.Persistence/Migrations/20260324041623_AddOrganizationBranding.Designer.cs new file mode 100644 index 0000000..7e09693 --- /dev/null +++ b/JobFlow.Infrastructure.Persistence/Migrations/20260324041623_AddOrganizationBranding.Designer.cs @@ -0,0 +1,3313 @@ +// +using System; +using JobFlow.Infrastructure.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace JobFlow.Infrastructure.Persistence.Migrations +{ + [DbContext(typeof(JobFlowDbContext))] + [Migration("20260324041623_AddOrganizationBranding")] + partial class AddOrganizationBranding + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.3") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("JobFlow.Domain.Models.Assignment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ActualEnd") + .HasColumnType("datetimeoffset"); + + b.Property("ActualStart") + .HasColumnType("datetimeoffset"); + + b.Property("Address1") + .HasColumnType("nvarchar(max)"); + + b.Property("City") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("JobId") + .HasColumnType("uniqueidentifier"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("ScheduleType") + .HasColumnType("int"); + + b.Property("ScheduledEnd") + .HasColumnType("datetimeoffset"); + + b.Property("ScheduledStart") + .HasColumnType("datetimeoffset"); + + b.Property("State") + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ZipCode") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("JobId"); + + b.HasIndex("ScheduledStart"); + + b.ToTable("Assignment", (string)null); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.AssignmentAssignee", b => + { + b.Property("AssignmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("EmployeeId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("IsLead") + .HasColumnType("bit"); + + b.HasKey("AssignmentId", "EmployeeId"); + + b.HasIndex("EmployeeId") + .HasDatabaseName("IX_AssignmentAssignee_EmployeeId"); + + b.ToTable("AssignmentAssignee", (string)null); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.AssignmentHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AssignmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("ChangedAt") + .HasColumnType("datetime2"); + + b.Property("ChangedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("EventType") + .HasColumnType("int"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("NewScheduledEnd") + .HasColumnType("datetime2"); + + b.Property("NewScheduledStart") + .HasColumnType("datetime2"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("OldScheduledEnd") + .HasColumnType("datetime2"); + + b.Property("OldScheduledStart") + .HasColumnType("datetime2"); + + b.Property("Reason") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("AssignmentId") + .HasDatabaseName("IX_AssignmentHistory_AssignmentId"); + + b.ToTable("AssignmentHistory", (string)null); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.AssignmentOrder", b => + { + b.Property("AssignmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("OrderId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.HasKey("AssignmentId", "OrderId"); + + b.HasIndex("AssignmentId"); + + b.HasIndex("OrderId"); + + b.ToTable("AssignmentOrder", (string)null); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.Conversation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("OrganizationClientId") + .HasColumnType("uniqueidentifier"); + + b.Property("Title") + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationClientId"); + + b.ToTable("Conversation", "messaging"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.ConversationParticipant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConversationId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("ConversationId", "UserId") + .IsUnique(); + + b.ToTable("ConversationParticipant", "messaging"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.CustomerPaymentProfile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("DefaultPaymentMethodId") + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDelinquent") + .HasColumnType("bit"); + + b.Property("OrganizationClientId") + .HasColumnType("uniqueidentifier"); + + b.Property("OrganizationId") + .HasColumnType("uniqueidentifier"); + + b.Property("OwnerId") + .HasColumnType("uniqueidentifier"); + + b.Property("OwnerType") + .HasColumnType("int"); + + b.Property("Provider") + .HasColumnType("int"); + + b.Property("ProviderCustomerId") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationClientId"); + + b.HasIndex("OrganizationId"); + + b.ToTable("CustomerPaymentProfile", "payment"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.Employee", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("Email") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("HireDate") + .HasColumnType("datetime2"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("JobTitle") + .HasColumnType("nvarchar(max)"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("OrganizationId") + .HasColumnType("uniqueidentifier"); + + b.Property("PhoneNumber") + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("ProfilePictureUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.Property("TerminationDate") + .HasColumnType("datetime2"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("RoleId"); + + b.HasIndex("UserId"); + + b.ToTable("Employees", (string)null); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.EmployeeInvite", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccessCount") + .HasColumnType("int"); + + b.Property("AccessIpAddress") + .HasColumnType("nvarchar(max)"); + + b.Property("AccessedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("ExpiresAt") + .HasColumnType("datetime2"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b.Property("InviteToken") + .HasMaxLength(128) + .HasColumnType("uniqueidentifier"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b.Property("OrganizationId") + .HasColumnType("uniqueidentifier"); + + b.Property("PhoneNumber") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.Property("ShortCode") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("Status") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(1); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("InviteToken") + .IsUnique(); + + b.HasIndex("OrganizationId"); + + b.HasIndex("RoleId"); + + b.HasIndex("ShortCode") + .IsUnique() + .HasFilter("[ShortCode] IS NOT NULL"); + + b.HasIndex("OrganizationId", "Email") + .IsUnique() + .HasFilter("[Status] = 1"); + + b.HasIndex("Status", "ExpiresAt"); + + b.ToTable("EmployeeInvites", (string)null); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.EmployeeRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Description") + .HasMaxLength(240) + .HasColumnType("nvarchar(240)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("OrganizationId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("EmployeeRoles", (string)null); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.EmployeeRolePreset", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasMaxLength(240) + .HasColumnType("nvarchar(240)"); + + b.Property("IndustryKey") + .HasMaxLength(80) + .HasColumnType("nvarchar(80)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsSystem") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("nvarchar(120)"); + + b.Property("OrganizationId") + .HasColumnType("uniqueidentifier"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("EmployeeRolePresets", (string)null); + + b.HasData( + new + { + Id = new Guid("1a2b3c4d-1111-1111-1111-111111111111"), + CreatedAt = new DateTime(2026, 3, 23, 0, 0, 0, 0, DateTimeKind.Utc), + Description = "Default roles for field service teams.", + IndustryKey = "home-services", + IsActive = true, + IsSystem = true, + Name = "Home services" + }, + new + { + Id = new Guid("1a2b3c4d-2222-2222-2222-222222222222"), + CreatedAt = new DateTime(2026, 3, 23, 0, 0, 0, 0, DateTimeKind.Utc), + Description = "Default roles for creative studios.", + IndustryKey = "creative", + IsActive = true, + IsSystem = true, + Name = "Creative" + }, + new + { + Id = new Guid("1a2b3c4d-3333-3333-3333-333333333333"), + CreatedAt = new DateTime(2026, 3, 23, 0, 0, 0, 0, DateTimeKind.Utc), + Description = "Default roles for consulting teams.", + IndustryKey = "consulting", + IsActive = true, + IsSystem = true, + Name = "Consulting" + }, + new + { + Id = new Guid("1a2b3c4d-4444-4444-4444-444444444444"), + CreatedAt = new DateTime(2026, 3, 23, 0, 0, 0, 0, DateTimeKind.Utc), + Description = "Default roles for repair shops.", + IndustryKey = "tech-repair", + IsActive = true, + IsSystem = true, + Name = "Tech repair" + }); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.EmployeeRolePresetItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasMaxLength(240) + .HasColumnType("nvarchar(240)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("nvarchar(120)"); + + b.Property("PresetId") + .HasColumnType("uniqueidentifier"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("PresetId"); + + b.ToTable("EmployeeRolePresetItems", (string)null); + + b.HasData( + new + { + Id = new Guid("2a2b3c4d-1111-1111-1111-111111111111"), + CreatedAt = new DateTime(2026, 3, 23, 0, 0, 0, 0, DateTimeKind.Utc), + Description = "Field technician for on-site work.", + IsActive = true, + Name = "Technician", + PresetId = new Guid("1a2b3c4d-1111-1111-1111-111111111111"), + SortOrder = 1 + }, + new + { + Id = new Guid("2a2b3c4d-1111-1111-1111-111111111112"), + CreatedAt = new DateTime(2026, 3, 23, 0, 0, 0, 0, DateTimeKind.Utc), + Description = "Lead for quality checks and approvals.", + IsActive = true, + Name = "Supervisor", + PresetId = new Guid("1a2b3c4d-1111-1111-1111-111111111111"), + SortOrder = 2 + }, + new + { + Id = new Guid("2a2b3c4d-1111-1111-1111-111111111113"), + CreatedAt = new DateTime(2026, 3, 23, 0, 0, 0, 0, DateTimeKind.Utc), + Description = "Routes schedules and job assignments.", + IsActive = true, + Name = "Dispatcher", + PresetId = new Guid("1a2b3c4d-1111-1111-1111-111111111111"), + SortOrder = 3 + }, + new + { + Id = new Guid("2a2b3c4d-1111-1111-1111-111111111114"), + CreatedAt = new DateTime(2026, 3, 23, 0, 0, 0, 0, DateTimeKind.Utc), + Description = "Back-office support and billing.", + IsActive = true, + Name = "Admin", + PresetId = new Guid("1a2b3c4d-1111-1111-1111-111111111111"), + SortOrder = 4 + }, + new + { + Id = new Guid("2a2b3c4d-2222-2222-2222-222222222221"), + CreatedAt = new DateTime(2026, 3, 23, 0, 0, 0, 0, DateTimeKind.Utc), + Description = "Primary creator and deliverable owner.", + IsActive = true, + Name = "Designer", + PresetId = new Guid("1a2b3c4d-2222-2222-2222-222222222222"), + SortOrder = 1 + }, + new + { + Id = new Guid("2a2b3c4d-2222-2222-2222-222222222222"), + CreatedAt = new DateTime(2026, 3, 23, 0, 0, 0, 0, DateTimeKind.Utc), + Description = "Owns timelines, approvals, and client comms.", + IsActive = true, + Name = "Producer", + PresetId = new Guid("1a2b3c4d-2222-2222-2222-222222222222"), + SortOrder = 2 + }, + new + { + Id = new Guid("2a2b3c4d-2222-2222-2222-222222222223"), + CreatedAt = new DateTime(2026, 3, 23, 0, 0, 0, 0, DateTimeKind.Utc), + Description = "Schedules tasks and supports delivery.", + IsActive = true, + Name = "Coordinator", + PresetId = new Guid("1a2b3c4d-2222-2222-2222-222222222222"), + SortOrder = 3 + }, + new + { + Id = new Guid("2a2b3c4d-2222-2222-2222-222222222224"), + CreatedAt = new DateTime(2026, 3, 23, 0, 0, 0, 0, DateTimeKind.Utc), + Description = "Operations and billing support.", + IsActive = true, + Name = "Admin", + PresetId = new Guid("1a2b3c4d-2222-2222-2222-222222222222"), + SortOrder = 4 + }, + new + { + Id = new Guid("2a2b3c4d-3333-3333-3333-333333333331"), + CreatedAt = new DateTime(2026, 3, 23, 0, 0, 0, 0, DateTimeKind.Utc), + Description = "Client-facing delivery specialist.", + IsActive = true, + Name = "Consultant", + PresetId = new Guid("1a2b3c4d-3333-3333-3333-333333333333"), + SortOrder = 1 + }, + new + { + Id = new Guid("2a2b3c4d-3333-3333-3333-333333333332"), + CreatedAt = new DateTime(2026, 3, 23, 0, 0, 0, 0, DateTimeKind.Utc), + Description = "Owns engagement delivery and quality.", + IsActive = true, + Name = "Lead", + PresetId = new Guid("1a2b3c4d-3333-3333-3333-333333333333"), + SortOrder = 2 + }, + new + { + Id = new Guid("2a2b3c4d-3333-3333-3333-333333333333"), + CreatedAt = new DateTime(2026, 3, 23, 0, 0, 0, 0, DateTimeKind.Utc), + Description = "Plans meetings and follow-ups.", + IsActive = true, + Name = "Coordinator", + PresetId = new Guid("1a2b3c4d-3333-3333-3333-333333333333"), + SortOrder = 3 + }, + new + { + Id = new Guid("2a2b3c4d-3333-3333-3333-333333333334"), + CreatedAt = new DateTime(2026, 3, 23, 0, 0, 0, 0, DateTimeKind.Utc), + Description = "Back-office support and billing.", + IsActive = true, + Name = "Admin", + PresetId = new Guid("1a2b3c4d-3333-3333-3333-333333333333"), + SortOrder = 4 + }, + new + { + Id = new Guid("2a2b3c4d-4444-4444-4444-444444444441"), + CreatedAt = new DateTime(2026, 3, 23, 0, 0, 0, 0, DateTimeKind.Utc), + Description = "Executes diagnostics and repairs.", + IsActive = true, + Name = "Repair Tech", + PresetId = new Guid("1a2b3c4d-4444-4444-4444-444444444444"), + SortOrder = 1 + }, + new + { + Id = new Guid("2a2b3c4d-4444-4444-4444-444444444442"), + CreatedAt = new DateTime(2026, 3, 23, 0, 0, 0, 0, DateTimeKind.Utc), + Description = "Final testing and release approvals.", + IsActive = true, + Name = "QA", + PresetId = new Guid("1a2b3c4d-4444-4444-4444-444444444444"), + SortOrder = 2 + }, + new + { + Id = new Guid("2a2b3c4d-4444-4444-4444-444444444443"), + CreatedAt = new DateTime(2026, 3, 23, 0, 0, 0, 0, DateTimeKind.Utc), + Description = "Client intake and status updates.", + IsActive = true, + Name = "Service Advisor", + PresetId = new Guid("1a2b3c4d-4444-4444-4444-444444444444"), + SortOrder = 3 + }, + new + { + Id = new Guid("2a2b3c4d-4444-4444-4444-444444444444"), + CreatedAt = new DateTime(2026, 3, 23, 0, 0, 0, 0, DateTimeKind.Utc), + Description = "Operations and billing support.", + IsActive = true, + Name = "Admin", + PresetId = new Guid("1a2b3c4d-4444-4444-4444-444444444444"), + SortOrder = 4 + }); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.Estimate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetimeoffset"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("EstimateNumber") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("OrganizationClientId") + .HasColumnType("uniqueidentifier"); + + b.Property("OrganizationId") + .HasColumnType("uniqueidentifier"); + + b.Property("PublicToken") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("PublicTokenExpiresAt") + .HasColumnType("datetimeoffset"); + + b.Property("SentAt") + .HasColumnType("datetimeoffset"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Subtotal") + .HasColumnType("decimal(18,2)"); + + b.Property("TaxTotal") + .HasColumnType("decimal(18,2)"); + + b.Property("Title") + .HasColumnType("nvarchar(max)"); + + b.Property("Total") + .HasColumnType("decimal(18,2)"); + + b.Property("UpdatedAt") + .HasColumnType("datetimeoffset"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationClientId"); + + b.HasIndex("PublicToken") + .IsUnique(); + + b.HasIndex("OrganizationId", "EstimateNumber") + .IsUnique(); + + b.ToTable("Estimates", (string)null); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.EstimateLineItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("EstimateId") + .HasColumnType("uniqueidentifier"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Quantity") + .HasColumnType("decimal(18,2)"); + + b.Property("Total") + .HasColumnType("decimal(18,2)"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("EstimateId"); + + b.ToTable("EstimateLineItems", (string)null); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.EstimateRevisionAttachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ContentType") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("EstimateRevisionRequestId") + .HasColumnType("uniqueidentifier"); + + b.Property("FileData") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(260) + .HasColumnType("nvarchar(260)"); + + b.Property("FileSizeBytes") + .HasColumnType("bigint"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("EstimateRevisionRequestId"); + + b.ToTable("EstimateRevisionAttachments", (string)null); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.EstimateRevisionRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("EstimateId") + .HasColumnType("uniqueidentifier"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("OrganizationClientId") + .HasColumnType("uniqueidentifier"); + + b.Property("OrganizationId") + .HasColumnType("uniqueidentifier"); + + b.Property("OrganizationResponseMessage") + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("RequestMessage") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("RequestedAt") + .HasColumnType("datetimeoffset"); + + b.Property("ResolvedAt") + .HasColumnType("datetimeoffset"); + + b.Property("ReviewedAt") + .HasColumnType("datetimeoffset"); + + b.Property("RevisionNumber") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationClientId"); + + b.HasIndex("EstimateId", "RevisionNumber") + .IsUnique(); + + b.ToTable("EstimateRevisionRequests", (string)null); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.InventoryItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Category") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CostPerUnit") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("OrganizationId") + .HasColumnType("uniqueidentifier"); + + b.Property("QuantityInStock") + .HasColumnType("int"); + + b.Property("Unit") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId", "Name") + .IsUnique(); + + b.ToTable("InventoryItems", (string)null); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.Invoice", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AmountPaid") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("DueDate") + .HasColumnType("datetime2"); + + b.Property("ExternalPaymentId") + .HasColumnType("nvarchar(max)"); + + b.Property("InvoiceDate") + .HasColumnType("datetime2"); + + b.Property("InvoiceNumber") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("JobId") + .HasColumnType("uniqueidentifier"); + + b.Property("OrderId") + .HasColumnType("uniqueidentifier"); + + b.Property("OrganizationClientId") + .HasColumnType("uniqueidentifier"); + + b.Property("OrganizationId") + .HasColumnType("uniqueidentifier"); + + b.Property("PaidAt") + .HasColumnType("datetimeoffset"); + + b.Property("PaymentProvider") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("TotalAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("JobId"); + + b.HasIndex("OrderId"); + + b.HasIndex("OrganizationClientId"); + + b.ToTable("Invoice"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.InvoiceLineItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("InvoiceId") + .HasColumnType("uniqueidentifier"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("InvoiceId"); + + b.ToTable("InvoiceLineItem", (string)null); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.InvoiceSequence", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("LastSequence") + .HasColumnType("int"); + + b.Property("OrganizationId") + .HasColumnType("uniqueidentifier"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Year") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("InvoiceSequence", (string)null); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Comments") + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("InvoicingWorkflow") + .HasColumnType("int"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("Latitude") + .HasColumnType("float"); + + b.Property("LifecycleStatus") + .HasColumnType("int"); + + b.Property("Longitude") + .HasColumnType("float"); + + b.Property("OrganizationClientId") + .HasColumnType("uniqueidentifier"); + + b.Property("Title") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationClientId"); + + b.ToTable("Job", (string)null); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.JobRecurrence", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DaysOfWeekMask") + .HasColumnType("int"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("Duration") + .HasColumnType("time"); + + b.Property("EndDate") + .HasColumnType("datetime2"); + + b.Property("Frequency") + .HasColumnType("int"); + + b.Property("GenerateDaysAhead") + .HasColumnType("int"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("JobId") + .HasColumnType("uniqueidentifier"); + + b.Property("ScheduleType") + .HasColumnType("int"); + + b.Property("StartDate") + .HasColumnType("datetime2"); + + b.Property("StartTime") + .HasColumnType("time"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("JobId", "IsActive") + .HasDatabaseName("IX_JobRecurrence_JobId_IsActive"); + + b.ToTable("JobRecurrence", (string)null); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.JobTracking", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("EmployeeId") + .HasColumnType("uniqueidentifier"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("JobId") + .HasColumnType("uniqueidentifier"); + + b.Property("Latitude") + .HasColumnType("decimal(9,6)"); + + b.Property("Longitude") + .HasColumnType("decimal(9,6)"); + + b.Property("RecordedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("GETUTCDATE()"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("EmployeeId"); + + b.HasIndex("JobId"); + + b.ToTable("JobTracking"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.JobUpdate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("JobId") + .HasColumnType("uniqueidentifier"); + + b.Property("Message") + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("OccurredAt") + .HasColumnType("datetimeoffset"); + + b.Property("OrganizationClientId") + .HasColumnType("uniqueidentifier"); + + b.Property("OrganizationId") + .HasColumnType("uniqueidentifier"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("JobId", "OccurredAt"); + + b.ToTable("JobUpdates", (string)null); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.JobUpdateAttachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ContentType") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("FileData") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(260) + .HasColumnType("nvarchar(260)"); + + b.Property("FileSizeBytes") + .HasColumnType("bigint"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("JobUpdateId") + .HasColumnType("uniqueidentifier"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("JobUpdateId"); + + b.ToTable("JobUpdateAttachments", (string)null); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.Message", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AttachmentUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("Content") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ConversationId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("ExternalSenderName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ExternalSenderPhone") + .HasMaxLength(32) + .HasColumnType("nvarchar(32)"); + + b.Property("ExternalSenderType") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsRead") + .HasColumnType("bit"); + + b.Property("SenderId") + .HasColumnType("uniqueidentifier"); + + b.Property("SentAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ConversationId"); + + b.HasIndex("SenderId"); + + b.ToTable("Message", "messaging"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("Notes") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetime2"); + + b.Property("OrganizationClientId") + .HasColumnType("uniqueidentifier"); + + b.Property("Status") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("TotalAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationClientId"); + + b.ToTable("Order", "payment"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.Organization", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Address1") + .HasColumnType("nvarchar(max)"); + + b.Property("Address2") + .HasColumnType("nvarchar(max)"); + + b.Property("City") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("DefaultTaxRate") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("EmailAddress") + .HasColumnType("nvarchar(max)"); + + b.Property("EnableTax") + .HasColumnType("bit"); + + b.Property("HasFreeAccount") + .HasColumnType("bit"); + + b.Property("IndustryKey") + .HasMaxLength(80) + .HasColumnType("nvarchar(80)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsStripeConnected") + .HasColumnType("bit"); + + b.Property("OnBoardingComplete") + .HasColumnType("bit"); + + b.Property("OnboardingPresetAppliedAt") + .HasColumnType("datetimeoffset"); + + b.Property("OnboardingPresetKey") + .HasColumnType("nvarchar(max)"); + + b.Property("OnboardingTrack") + .HasColumnType("nvarchar(max)"); + + b.Property("OnboardingTrackSelectedAt") + .HasColumnType("datetimeoffset"); + + b.Property("OrganizationName") + .HasColumnType("nvarchar(max)"); + + b.Property("OrganizationTypeId") + .HasColumnType("uniqueidentifier"); + + b.Property("PaymentProvider") + .HasColumnType("int"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("State") + .HasColumnType("nvarchar(max)"); + + b.Property("StripeConnectAccountId") + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ZipCode") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationTypeId"); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.OrganizationBranding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BusinessName") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("FooterNote") + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("LogoUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("OrganizationId") + .HasColumnType("uniqueidentifier"); + + b.Property("PrimaryColor") + .HasColumnType("nvarchar(max)"); + + b.Property("SecondaryColor") + .HasColumnType("nvarchar(max)"); + + b.Property("Tagline") + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .IsUnique(); + + b.ToTable("OrganizationBranding", (string)null); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.OrganizationClient", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Address1") + .HasColumnType("nvarchar(max)"); + + b.Property("Address2") + .HasColumnType("nvarchar(max)"); + + b.Property("City") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("EmailAddress") + .HasColumnType("nvarchar(max)"); + + b.Property("FirstName") + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("LastName") + .HasColumnType("nvarchar(max)"); + + b.Property("OrganizationId") + .HasColumnType("uniqueidentifier"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("State") + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ZipCode") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationClient", (string)null); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.OrganizationClientPortalSession", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetimeoffset"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("EmailAddress") + .IsRequired() + .HasMaxLength(320) + .HasColumnType("nvarchar(320)"); + + b.Property("ExpiresAt") + .HasColumnType("datetimeoffset"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("OrganizationClientId") + .HasColumnType("uniqueidentifier"); + + b.Property("OrganizationId") + .HasColumnType("uniqueidentifier"); + + b.Property("RedeemedAt") + .HasColumnType("datetimeoffset"); + + b.Property("TokenHash") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationClientId"); + + b.HasIndex("TokenHash") + .IsUnique(); + + b.ToTable("OrganizationClientPortalSession"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.OrganizationInvoicingSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("DefaultWorkflow") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("OrganizationId") + .HasColumnType("uniqueidentifier"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .IsUnique(); + + b.ToTable("OrganizationInvoicingSettings"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.OrganizationOnboardingStep", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CompletedAt") + .HasColumnType("datetimeoffset"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsCompleted") + .HasColumnType("bit"); + + b.Property("OrganizationId") + .HasColumnType("uniqueidentifier"); + + b.Property("StepName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId", "StepName") + .IsUnique(); + + b.ToTable("OrganizationOnboardingSteps", (string)null); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.OrganizationScheduleSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AutoNotifyReschedule") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("DefaultWindowMinutes") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(120); + + b.Property("EnforceTravelBuffer") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("OrganizationId") + .HasColumnType("uniqueidentifier"); + + b.Property("TravelBufferMinutes") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(20); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .IsUnique(); + + b.ToTable("OrganizationScheduleSettings"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.OrganizationService", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("OrganizationId") + .HasColumnType("uniqueidentifier"); + + b.Property("ServiceName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationService", (string)null); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.OrganizationType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("TypeName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("OrganizationType", (string)null); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.OrganizationWorkflowStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Category") + .IsRequired() + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("Label") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("nvarchar(120)"); + + b.Property("OrganizationId") + .HasColumnType("uniqueidentifier"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("StatusKey") + .IsRequired() + .HasMaxLength(80) + .HasColumnType("nvarchar(80)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId", "Category", "StatusKey") + .IsUnique(); + + b.ToTable("OrganizationWorkflowStatus"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.PaymentHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AmountPaid") + .HasColumnType("bigint"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Currency") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CustomerId") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("EntityId") + .HasColumnType("uniqueidentifier"); + + b.Property("EntityType") + .HasColumnType("int"); + + b.Property("EventType") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("InvoiceId") + .HasColumnType("uniqueidentifier"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("PaidAt") + .HasColumnType("datetime2"); + + b.Property("PaymentProvider") + .HasColumnType("int"); + + b.Property("RawEventJson") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("StripeInvoiceId") + .HasColumnType("nvarchar(max)"); + + b.Property("StripePaymentIntentId") + .HasColumnType("nvarchar(max)"); + + b.Property("SubscriptionId") + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("InvoiceId"); + + b.ToTable("PaymentHistory"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.PriceBookCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("OrganizationId") + .HasColumnType("uniqueidentifier"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId", "Name") + .IsUnique(); + + b.ToTable("PriceBookCategories", (string)null); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.PriceBookItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CategoryId") + .HasColumnType("uniqueidentifier"); + + b.Property("Cost") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("InventoryItemId") + .HasColumnType("uniqueidentifier"); + + b.Property("InventoryUnitsPerSale") + .ValueGeneratedOnAdd() + .HasColumnType("decimal(18,4)") + .HasDefaultValue(1.0m); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsTaxable") + .HasColumnType("bit"); + + b.Property("ItemType") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("OrganizationId") + .HasColumnType("uniqueidentifier"); + + b.Property("PartNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("PricePerUnit") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("Unit") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.HasIndex("InventoryItemId"); + + b.HasIndex("OrganizationId", "Name") + .IsUnique(); + + b.ToTable("PriceBookItems", (string)null); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.SubscriptionRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CanceledAt") + .HasColumnType("datetime2"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("PaymentProfileId") + .HasColumnType("uniqueidentifier"); + + b.Property("PlanName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Provider") + .HasColumnType("int"); + + b.Property("ProviderPriceId") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("ProviderSubscriptionId") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("StartDate") + .HasColumnType("datetime2"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("nvarchar(64)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("PaymentProfileId"); + + b.ToTable("SubscriptionRecord", "payment"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.SupportHubInvite", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(12) + .HasColumnType("nvarchar(12)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("ExpiresAt") + .HasColumnType("datetimeoffset"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("RedeemedAt") + .HasColumnType("datetimeoffset"); + + b.Property("RedeemedByUid") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("Role") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.ToTable("SupportHubInvites"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.SupportHubSession", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AgentName") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("nvarchar(120)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("EndedAt") + .HasColumnType("datetimeoffset"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("OrganizationId") + .HasColumnType("uniqueidentifier"); + + b.Property("StartedAt") + .HasColumnType("datetimeoffset"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SupportHubSessions"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.SupportHubTicket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("LastActivityAt") + .HasColumnType("datetimeoffset"); + + b.Property("OrganizationId") + .HasColumnType("uniqueidentifier"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Summary") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(160) + .HasColumnType("nvarchar(160)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SupportHubTickets"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.SystemRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier") + .HasDefaultValueSql("NEWID()"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Roles", (string)null); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClientId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("Email") + .HasColumnType("nvarchar(max)"); + + b.Property("FirebaseUid") + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("OrganizationId") + .HasColumnType("uniqueidentifier"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PreferredLanguage") + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Users", (string)null); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.UserRole", b => + { + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("UserRoles", (string)null); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.Assignment", b => + { + b.HasOne("JobFlow.Domain.Models.Job", "Job") + .WithMany("Assignments") + .HasForeignKey("JobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Job"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.AssignmentAssignee", b => + { + b.HasOne("JobFlow.Domain.Models.Assignment", "Assignment") + .WithMany("AssignmentAssignees") + .HasForeignKey("AssignmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("JobFlow.Domain.Models.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Assignment"); + + b.Navigation("Employee"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.AssignmentHistory", b => + { + b.HasOne("JobFlow.Domain.Models.Assignment", "Assignment") + .WithMany() + .HasForeignKey("AssignmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Assignment"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.AssignmentOrder", b => + { + b.HasOne("JobFlow.Domain.Models.Assignment", "Assignment") + .WithMany("AssignmentOrders") + .HasForeignKey("AssignmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("JobFlow.Domain.Models.Order", "Order") + .WithMany("AssignmentOrders") + .HasForeignKey("OrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Assignment"); + + b.Navigation("Order"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.Conversation", b => + { + b.HasOne("JobFlow.Domain.Models.OrganizationClient", "OrganizationClient") + .WithMany() + .HasForeignKey("OrganizationClientId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("OrganizationClient"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.ConversationParticipant", b => + { + b.HasOne("JobFlow.Domain.Models.Conversation", "Conversation") + .WithMany("Participants") + .HasForeignKey("ConversationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("JobFlow.Domain.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Conversation"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.CustomerPaymentProfile", b => + { + b.HasOne("JobFlow.Domain.Models.OrganizationClient", null) + .WithMany("PaymentProfiles") + .HasForeignKey("OrganizationClientId"); + + b.HasOne("JobFlow.Domain.Models.Organization", null) + .WithMany("PaymentProfiles") + .HasForeignKey("OrganizationId"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.Employee", b => + { + b.HasOne("JobFlow.Domain.Models.Organization", "Organization") + .WithMany("Employees") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("JobFlow.Domain.Models.EmployeeRole", "Role") + .WithMany("Employees") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("JobFlow.Domain.Models.User", "User") + .WithMany("Employees") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Organization"); + + b.Navigation("Role"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.EmployeeInvite", b => + { + b.HasOne("JobFlow.Domain.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("JobFlow.Domain.Models.EmployeeRole", "Role") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.EmployeeRole", b => + { + b.HasOne("JobFlow.Domain.Models.Organization", "Organization") + .WithMany("EmployeeRoles") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.EmployeeRolePreset", b => + { + b.HasOne("JobFlow.Domain.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.EmployeeRolePresetItem", b => + { + b.HasOne("JobFlow.Domain.Models.EmployeeRolePreset", "Preset") + .WithMany("Items") + .HasForeignKey("PresetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Preset"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.Estimate", b => + { + b.HasOne("JobFlow.Domain.Models.OrganizationClient", "OrganizationClient") + .WithMany() + .HasForeignKey("OrganizationClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("OrganizationClient"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.EstimateLineItem", b => + { + b.HasOne("JobFlow.Domain.Models.Estimate", "Estimate") + .WithMany("LineItems") + .HasForeignKey("EstimateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Estimate"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.EstimateRevisionAttachment", b => + { + b.HasOne("JobFlow.Domain.Models.EstimateRevisionRequest", "RevisionRequest") + .WithMany("Attachments") + .HasForeignKey("EstimateRevisionRequestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RevisionRequest"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.EstimateRevisionRequest", b => + { + b.HasOne("JobFlow.Domain.Models.Estimate", "Estimate") + .WithMany("RevisionRequests") + .HasForeignKey("EstimateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("JobFlow.Domain.Models.OrganizationClient", "OrganizationClient") + .WithMany() + .HasForeignKey("OrganizationClientId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Estimate"); + + b.Navigation("OrganizationClient"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.Invoice", b => + { + b.HasOne("JobFlow.Domain.Models.Job", "Job") + .WithMany() + .HasForeignKey("JobId"); + + b.HasOne("JobFlow.Domain.Models.Order", "Order") + .WithMany("Invoices") + .HasForeignKey("OrderId"); + + b.HasOne("JobFlow.Domain.Models.OrganizationClient", "OrganizationClient") + .WithMany() + .HasForeignKey("OrganizationClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Job"); + + b.Navigation("Order"); + + b.Navigation("OrganizationClient"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.InvoiceLineItem", b => + { + b.HasOne("JobFlow.Domain.Models.Invoice", "Invoice") + .WithMany("LineItems") + .HasForeignKey("InvoiceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Invoice"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.Job", b => + { + b.HasOne("JobFlow.Domain.Models.OrganizationClient", "OrganizationClient") + .WithMany("Jobs") + .HasForeignKey("OrganizationClientId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("OrganizationClient"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.JobRecurrence", b => + { + b.HasOne("JobFlow.Domain.Models.Job", "Job") + .WithMany() + .HasForeignKey("JobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Job"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.JobTracking", b => + { + b.HasOne("JobFlow.Domain.Models.User", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("JobFlow.Domain.Models.Job", "Job") + .WithMany("JobTrackings") + .HasForeignKey("JobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Employee"); + + b.Navigation("Job"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.JobUpdate", b => + { + b.HasOne("JobFlow.Domain.Models.Job", "Job") + .WithMany("JobUpdates") + .HasForeignKey("JobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Job"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.JobUpdateAttachment", b => + { + b.HasOne("JobFlow.Domain.Models.JobUpdate", "JobUpdate") + .WithMany("Attachments") + .HasForeignKey("JobUpdateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("JobUpdate"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.Message", b => + { + b.HasOne("JobFlow.Domain.Models.Conversation", "Conversation") + .WithMany("Messages") + .HasForeignKey("ConversationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("JobFlow.Domain.Models.User", "Sender") + .WithMany() + .HasForeignKey("SenderId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Conversation"); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.Order", b => + { + b.HasOne("JobFlow.Domain.Models.OrganizationClient", "OrganizationClient") + .WithMany() + .HasForeignKey("OrganizationClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("OrganizationClient"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.Organization", b => + { + b.HasOne("JobFlow.Domain.Models.OrganizationType", "OrganizationType") + .WithMany() + .HasForeignKey("OrganizationTypeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("OrganizationType"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.OrganizationBranding", b => + { + b.HasOne("JobFlow.Domain.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.OrganizationClient", b => + { + b.HasOne("JobFlow.Domain.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.OrganizationClientPortalSession", b => + { + b.HasOne("JobFlow.Domain.Models.OrganizationClient", "OrganizationClient") + .WithMany() + .HasForeignKey("OrganizationClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("OrganizationClient"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.OrganizationOnboardingStep", b => + { + b.HasOne("JobFlow.Domain.Models.Organization", "Organization") + .WithMany("OnboardingSteps") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.OrganizationService", b => + { + b.HasOne("JobFlow.Domain.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.PaymentHistory", b => + { + b.HasOne("JobFlow.Domain.Models.Invoice", "Invoice") + .WithMany("Payments") + .HasForeignKey("InvoiceId"); + + b.Navigation("Invoice"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.PriceBookItem", b => + { + b.HasOne("JobFlow.Domain.Models.PriceBookCategory", "Category") + .WithMany("Items") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("JobFlow.Domain.Models.InventoryItem", "InventoryItem") + .WithMany() + .HasForeignKey("InventoryItemId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Category"); + + b.Navigation("InventoryItem"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.SubscriptionRecord", b => + { + b.HasOne("JobFlow.Domain.Models.CustomerPaymentProfile", "PaymentProfile") + .WithMany() + .HasForeignKey("PaymentProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PaymentProfile"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.SupportHubSession", b => + { + b.HasOne("JobFlow.Domain.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.SupportHubTicket", b => + { + b.HasOne("JobFlow.Domain.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.User", b => + { + b.HasOne("JobFlow.Domain.Models.OrganizationClient", "Client") + .WithMany() + .HasForeignKey("ClientId"); + + b.HasOne("JobFlow.Domain.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.UserRole", b => + { + b.HasOne("JobFlow.Domain.Models.SystemRole", "Role") + .WithMany("UserRoles") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("JobFlow.Domain.Models.User", "User") + .WithMany("UserRoles") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.Assignment", b => + { + b.Navigation("AssignmentAssignees"); + + b.Navigation("AssignmentOrders"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.Conversation", b => + { + b.Navigation("Messages"); + + b.Navigation("Participants"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.EmployeeRole", b => + { + b.Navigation("Employees"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.EmployeeRolePreset", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.Estimate", b => + { + b.Navigation("LineItems"); + + b.Navigation("RevisionRequests"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.EstimateRevisionRequest", b => + { + b.Navigation("Attachments"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.Invoice", b => + { + b.Navigation("LineItems"); + + b.Navigation("Payments"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.Job", b => + { + b.Navigation("Assignments"); + + b.Navigation("JobTrackings"); + + b.Navigation("JobUpdates"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.JobUpdate", b => + { + b.Navigation("Attachments"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.Order", b => + { + b.Navigation("AssignmentOrders"); + + b.Navigation("Invoices"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.Organization", b => + { + b.Navigation("EmployeeRoles"); + + b.Navigation("Employees"); + + b.Navigation("OnboardingSteps"); + + b.Navigation("PaymentProfiles"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.OrganizationClient", b => + { + b.Navigation("Jobs"); + + b.Navigation("PaymentProfiles"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.PriceBookCategory", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.SystemRole", b => + { + b.Navigation("UserRoles"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.User", b => + { + b.Navigation("Employees"); + + b.Navigation("UserRoles"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/JobFlow.Infrastructure.Persistence/Migrations/20260324041623_AddOrganizationBranding.cs b/JobFlow.Infrastructure.Persistence/Migrations/20260324041623_AddOrganizationBranding.cs new file mode 100644 index 0000000..38f16e2 --- /dev/null +++ b/JobFlow.Infrastructure.Persistence/Migrations/20260324041623_AddOrganizationBranding.cs @@ -0,0 +1,58 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace JobFlow.Infrastructure.Persistence.Migrations +{ + /// + public partial class AddOrganizationBranding : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "OrganizationBranding", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + OrganizationId = table.Column(type: "uniqueidentifier", nullable: false), + LogoUrl = table.Column(type: "nvarchar(max)", nullable: true), + PrimaryColor = table.Column(type: "nvarchar(max)", nullable: true), + SecondaryColor = table.Column(type: "nvarchar(max)", nullable: true), + BusinessName = table.Column(type: "nvarchar(max)", nullable: true), + Tagline = table.Column(type: "nvarchar(max)", nullable: true), + FooterNote = table.Column(type: "nvarchar(max)", nullable: true), + CreatedBy = table.Column(type: "nvarchar(max)", nullable: true), + UpdatedBy = table.Column(type: "nvarchar(max)", nullable: true), + CreatedAt = table.Column(type: "datetime2", nullable: false), + UpdatedAt = table.Column(type: "datetime2", nullable: true), + IsActive = table.Column(type: "bit", nullable: false), + DeactivatedAtUtc = table.Column(type: "datetime2", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OrganizationBranding", x => x.Id); + table.ForeignKey( + name: "FK_OrganizationBranding_Organization_OrganizationId", + column: x => x.OrganizationId, + principalTable: "Organization", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_OrganizationBranding_OrganizationId", + table: "OrganizationBranding", + column: "OrganizationId", + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "OrganizationBranding"); + } + } +} diff --git a/JobFlow.Infrastructure.Persistence/Migrations/JobFlowDbContextModelSnapshot.cs b/JobFlow.Infrastructure.Persistence/Migrations/JobFlowDbContextModelSnapshot.cs index 2b2f34e..0d852e9 100644 --- a/JobFlow.Infrastructure.Persistence/Migrations/JobFlowDbContextModelSnapshot.cs +++ b/JobFlow.Infrastructure.Persistence/Migrations/JobFlowDbContextModelSnapshot.cs @@ -1757,6 +1757,59 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("Organization", (string)null); }); + modelBuilder.Entity("JobFlow.Domain.Models.OrganizationBranding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BusinessName") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeactivatedAtUtc") + .HasColumnType("datetime2"); + + b.Property("FooterNote") + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("LogoUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("OrganizationId") + .HasColumnType("uniqueidentifier"); + + b.Property("PrimaryColor") + .HasColumnType("nvarchar(max)"); + + b.Property("SecondaryColor") + .HasColumnType("nvarchar(max)"); + + b.Property("Tagline") + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .IsUnique(); + + b.ToTable("OrganizationBranding", (string)null); + }); + modelBuilder.Entity("JobFlow.Domain.Models.OrganizationClient", b => { b.Property("Id") @@ -2345,16 +2398,14 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("PriceBookItems", (string)null); }); - modelBuilder.Entity("JobFlow.Domain.Models.SupportHubInvite", b => + modelBuilder.Entity("JobFlow.Domain.Models.SubscriptionRecord", b => { b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("uniqueidentifier"); - b.Property("Code") - .IsRequired() - .HasMaxLength(12) - .HasColumnType("nvarchar(12)"); + b.Property("CanceledAt") + .HasColumnType("datetime2"); b.Property("CreatedAt") .HasColumnType("datetime2"); @@ -2365,22 +2416,37 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("DeactivatedAtUtc") .HasColumnType("datetime2"); - b.Property("ExpiresAt") - .HasColumnType("datetimeoffset"); - b.Property("IsActive") .HasColumnType("bit"); - b.Property("Role") + b.Property("PaymentProfileId") + .HasColumnType("uniqueidentifier"); + + b.Property("PlanName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Provider") .HasColumnType("int"); - b.Property("RedeemedAt") - .HasColumnType("datetimeoffset"); + b.Property("ProviderPriceId") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); - b.Property("RedeemedByUid") + b.Property("ProviderSubscriptionId") + .IsRequired() .HasMaxLength(128) .HasColumnType("nvarchar(128)"); + b.Property("StartDate") + .HasColumnType("datetime2"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("nvarchar(64)"); + b.Property("UpdatedAt") .HasColumnType("datetime2"); @@ -2389,22 +2455,21 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("Id"); - b.HasIndex("Code") - .IsUnique(); + b.HasIndex("PaymentProfileId"); - b.ToTable("SupportHubInvites", (string)null); + b.ToTable("SubscriptionRecord", "payment"); }); - modelBuilder.Entity("JobFlow.Domain.Models.SupportHubSession", b => + modelBuilder.Entity("JobFlow.Domain.Models.SupportHubInvite", b => { b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("uniqueidentifier"); - b.Property("AgentName") + b.Property("Code") .IsRequired() - .HasMaxLength(120) - .HasColumnType("nvarchar(120)"); + .HasMaxLength(12) + .HasColumnType("nvarchar(12)"); b.Property("CreatedAt") .HasColumnType("datetime2"); @@ -2415,19 +2480,20 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("DeactivatedAtUtc") .HasColumnType("datetime2"); - b.Property("EndedAt") + b.Property("ExpiresAt") .HasColumnType("datetimeoffset"); b.Property("IsActive") .HasColumnType("bit"); - b.Property("OrganizationId") - .HasColumnType("uniqueidentifier"); - - b.Property("StartedAt") + b.Property("RedeemedAt") .HasColumnType("datetimeoffset"); - b.Property("Status") + b.Property("RedeemedByUid") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("Role") .HasColumnType("int"); b.Property("UpdatedAt") @@ -2438,17 +2504,23 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("Id"); - b.HasIndex("OrganizationId"); + b.HasIndex("Code") + .IsUnique(); - b.ToTable("SupportHubSessions", (string)null); + b.ToTable("SupportHubInvites"); }); - modelBuilder.Entity("JobFlow.Domain.Models.SupportHubTicket", b => + modelBuilder.Entity("JobFlow.Domain.Models.SupportHubSession", b => { b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("uniqueidentifier"); + b.Property("AgentName") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("nvarchar(120)"); + b.Property("CreatedAt") .HasColumnType("datetime2"); @@ -2458,27 +2530,21 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("DeactivatedAtUtc") .HasColumnType("datetime2"); + b.Property("EndedAt") + .HasColumnType("datetimeoffset"); + b.Property("IsActive") .HasColumnType("bit"); - b.Property("LastActivityAt") - .HasColumnType("datetimeoffset"); - b.Property("OrganizationId") .HasColumnType("uniqueidentifier"); + b.Property("StartedAt") + .HasColumnType("datetimeoffset"); + b.Property("Status") .HasColumnType("int"); - b.Property("Summary") - .HasMaxLength(500) - .HasColumnType("nvarchar(500)"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("nvarchar(160)"); - b.Property("UpdatedAt") .HasColumnType("datetime2"); @@ -2489,18 +2555,15 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("OrganizationId"); - b.ToTable("SupportHubTickets", (string)null); + b.ToTable("SupportHubSessions"); }); - modelBuilder.Entity("JobFlow.Domain.Models.SubscriptionRecord", b => + modelBuilder.Entity("JobFlow.Domain.Models.SupportHubTicket", b => { b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("uniqueidentifier"); - b.Property("CanceledAt") - .HasColumnType("datetime2"); - b.Property("CreatedAt") .HasColumnType("datetime2"); @@ -2513,33 +2576,23 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("IsActive") .HasColumnType("bit"); - b.Property("PaymentProfileId") - .HasColumnType("uniqueidentifier"); + b.Property("LastActivityAt") + .HasColumnType("datetimeoffset"); - b.Property("PlanName") - .IsRequired() - .HasColumnType("nvarchar(max)"); + b.Property("OrganizationId") + .HasColumnType("uniqueidentifier"); - b.Property("Provider") + b.Property("Status") .HasColumnType("int"); - b.Property("ProviderPriceId") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("nvarchar(128)"); - - b.Property("ProviderSubscriptionId") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("nvarchar(128)"); - - b.Property("StartDate") - .HasColumnType("datetime2"); + b.Property("Summary") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); - b.Property("Status") + b.Property("Title") .IsRequired() - .HasMaxLength(64) - .HasColumnType("nvarchar(64)"); + .HasMaxLength(160) + .HasColumnType("nvarchar(160)"); b.Property("UpdatedAt") .HasColumnType("datetime2"); @@ -2549,9 +2602,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("Id"); - b.HasIndex("PaymentProfileId"); + b.HasIndex("OrganizationId"); - b.ToTable("SubscriptionRecord", "payment"); + b.ToTable("SupportHubTickets"); }); modelBuilder.Entity("JobFlow.Domain.Models.SystemRole", b => @@ -2603,6 +2656,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("PhoneNumber") .HasColumnType("nvarchar(max)"); + b.Property("PreferredLanguage") + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + b.Property("UpdatedAt") .HasColumnType("datetime2"); @@ -2999,6 +3056,17 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("OrganizationType"); }); + modelBuilder.Entity("JobFlow.Domain.Models.OrganizationBranding", b => + { + b.HasOne("JobFlow.Domain.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + modelBuilder.Entity("JobFlow.Domain.Models.OrganizationClient", b => { b.HasOne("JobFlow.Domain.Models.Organization", "Organization") @@ -3043,28 +3111,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Organization"); }); - modelBuilder.Entity("JobFlow.Domain.Models.SupportHubSession", b => - { - b.HasOne("JobFlow.Domain.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("JobFlow.Domain.Models.SupportHubTicket", b => - { - b.HasOne("JobFlow.Domain.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - modelBuilder.Entity("JobFlow.Domain.Models.PaymentHistory", b => { b.HasOne("JobFlow.Domain.Models.Invoice", "Invoice") @@ -3102,6 +3148,28 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("PaymentProfile"); }); + modelBuilder.Entity("JobFlow.Domain.Models.SupportHubSession", b => + { + b.HasOne("JobFlow.Domain.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("JobFlow.Domain.Models.SupportHubTicket", b => + { + b.HasOne("JobFlow.Domain.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + modelBuilder.Entity("JobFlow.Domain.Models.User", b => { b.HasOne("JobFlow.Domain.Models.OrganizationClient", "Client")