Files
PowderCoatingLogix/src/PowderCoating.Application/DTOs/Company/CompanyDtos.cs
T
spouliot 726bebdce9 Consolidate company admin screens: health badge on list, tabbed detail page
Companies/Index:
- Added Health badge column (Healthy / At Risk / Critical / Never Active)
  with the numeric score in a tooltip; computed from the same signals as
  CompanyHealth/Index using the new shared CompanyHealthHelper

Companies/Details:
- Converted flat card layout to five tabs: Overview, Users, Subscription,
  Onboarding, Health; URL hash is preserved so the active tab survives
  page refresh and back navigation
- Subscription tab shows plan/status/dates with an expiry countdown and a
  "Manage Subscription & Features" button to the full Manage page
- Onboarding tab shows wizard completion, milestone progress bar, and
  first-activity dates (previously only on the standalone page)
- Health tab shows score gauge, risk badge, and individual risk signals
  with a link through to the full CompanyHealth dashboard
- JS moved to wwwroot/js/companies-details.js (avoids inline-script failures)

Infrastructure:
- Extracted ComputeHealth / ToRiskLevel / ChurnRisk to CompanyHealthHelper.cs
  (same Controllers namespace); CompanyHealthController delegates to it
- CompanyCountSummary extended with Jobs30Counts, Jobs90Counts, LastLoginDates
  (3 extra GROUP BY queries scoped to the current page IDs, not all companies)
- CompanyListDto gains HealthScore, HealthRisk, LastLoginDate

Navigation:
- Removed "Onboarding Progress" hub card from People & Activity; the data
  is now surfaced directly on the Companies/Details Onboarding tab

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 22:22:14 -04:00

238 lines
8.9 KiB
C#

using System.ComponentModel.DataAnnotations;
using PowderCoating.Application.DTOs.User;
using PowderCoating.Core.Enums;
namespace PowderCoating.Application.DTOs.Company;
/// <summary>
/// Full company details DTO
/// </summary>
public class CompanyDto
{
public int Id { get; set; }
public string CompanyName { get; set; } = string.Empty;
public string? CompanyCode { get; set; }
public string PrimaryContactName { get; set; } = string.Empty;
public string PrimaryContactEmail { get; set; } = string.Empty;
public string? Phone { get; set; }
public string? Address { get; set; }
public string? City { get; set; }
public string? State { get; set; }
public string? ZipCode { get; set; }
public bool IsActive { get; set; }
public DateTime SubscriptionStartDate { get; set; }
public DateTime? SubscriptionEndDate { get; set; }
public int SubscriptionPlan { get; set; }
public SubscriptionStatus SubscriptionStatus { get; set; }
public string? StripeCustomerId { get; set; }
public string? StripeSubscriptionId { get; set; }
public string? TimeZone { get; set; }
public string? LogoPath { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime? UpdatedAt { get; set; }
public string? CreatedBy { get; set; }
public string? UpdatedBy { get; set; }
// Statistics
public int UserCount { get; set; }
public int CustomerCount { get; set; }
public int JobCount { get; set; }
// Users list for management
public List<CompanyUserDto> Users { get; set; } = new List<CompanyUserDto>();
}
/// <summary>
/// Company list item DTO for grid/list views
/// </summary>
public class CompanyListDto
{
public int Id { get; set; }
public string CompanyName { get; set; } = string.Empty;
public string? CompanyCode { get; set; }
public string PrimaryContactEmail { get; set; } = string.Empty;
public string? Phone { get; set; }
public int SubscriptionPlan { get; set; }
public SubscriptionStatus SubscriptionStatus { get; set; }
public bool IsActive { get; set; }
public DateTime SubscriptionStartDate { get; set; }
public DateTime? SubscriptionEndDate { get; set; }
public int UserCount { get; set; }
public int JobCount { get; set; }
public int QuoteCount { get; set; }
public int CustomerCount { get; set; }
public DateTime CreatedAt { get; set; }
public bool WizardCompleted { get; set; }
public DateTime? WizardCompletedAt { get; set; }
public string? WizardCompletedByName { get; set; }
// Health signals — populated by CompaniesController.Index after the count summary query
public int HealthScore { get; set; }
public string HealthRisk { get; set; } = "Healthy";
public DateTime? LastLoginDate { get; set; }
}
/// <summary>
/// DTO for creating a new company
/// </summary>
public class CreateCompanyDto
{
[Required(ErrorMessage = "Company name is required")]
[StringLength(200, ErrorMessage = "Company name cannot exceed 200 characters")]
public string CompanyName { get; set; } = string.Empty;
[StringLength(10, ErrorMessage = "Company code cannot exceed 10 characters")]
public string? CompanyCode { get; set; }
[Required(ErrorMessage = "Primary contact name is required")]
[StringLength(100, ErrorMessage = "Contact name cannot exceed 100 characters")]
public string PrimaryContactName { get; set; } = string.Empty;
[Required(ErrorMessage = "Primary contact email is required")]
[EmailAddress(ErrorMessage = "Invalid email address")]
public string PrimaryContactEmail { get; set; } = string.Empty;
[Phone(ErrorMessage = "Invalid phone number")]
public string? Phone { get; set; }
public string? Address { get; set; }
public string? City { get; set; }
[StringLength(2, ErrorMessage = "State code must be 2 characters")]
public string? State { get; set; }
public string? ZipCode { get; set; }
public bool IsActive { get; set; } = true;
[Required(ErrorMessage = "Subscription start date is required")]
public DateTime SubscriptionStartDate { get; set; } = DateTime.UtcNow;
public DateTime? SubscriptionEndDate { get; set; }
public int SubscriptionPlan { get; set; } = 0;
public string? TimeZone { get; set; } = "America/New_York";
// Initial admin user details
[Required(ErrorMessage = "Admin first name is required")]
public string AdminFirstName { get; set; } = string.Empty;
[Required(ErrorMessage = "Admin last name is required")]
public string AdminLastName { get; set; } = string.Empty;
[Required(ErrorMessage = "Admin email is required")]
[EmailAddress(ErrorMessage = "Invalid email address")]
public string AdminEmail { get; set; } = string.Empty;
[Required(ErrorMessage = "Admin password is required")]
[StringLength(100, MinimumLength = 8, ErrorMessage = "Password must be at least 8 characters long")]
[RegularExpression(@"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&#^()_+=\-\[\]{};':""\\|,.<>\/`~])[A-Za-z\d@$!%*?&#^()_+=\-\[\]{};':""\\|,.<>\/`~]{8,}$",
ErrorMessage = "Password must contain at least one uppercase letter, one lowercase letter, one digit, and one special character")]
public string AdminPassword { get; set; } = string.Empty;
}
/// <summary>
/// DTO for updating an existing company
/// </summary>
public class UpdateCompanyDto
{
public int Id { get; set; }
public string CompanyName { get; set; } = string.Empty;
public string? CompanyCode { get; set; }
public string PrimaryContactName { get; set; } = string.Empty;
public string PrimaryContactEmail { get; set; } = string.Empty;
public string? Phone { get; set; }
public string? Address { get; set; }
public string? City { get; set; }
public string? State { get; set; }
public string? ZipCode { get; set; }
public bool IsActive { get; set; }
public DateTime SubscriptionStartDate { get; set; }
public DateTime? SubscriptionEndDate { get; set; }
public int SubscriptionPlan { get; set; }
// AI feature flags
public bool AiPhotoQuotesEnabled { get; set; }
public bool AiInventoryAssistEnabled { get; set; }
public bool AiCatalogPriceCheckEnabled { get; set; }
public int? MaxAiPhotoQuotesPerMonthOverride { get; set; }
// Per-company feature overrides (null = use plan default)
public bool? OnlinePaymentsOverride { get; set; }
public bool? AccountingOverride { get; set; }
/// <summary>When true, SuperAdmin has force-disabled SMS for this company regardless of plan or company settings.</summary>
public bool SmsDisabledByAdmin { get; set; }
public string? TimeZone { get; set; }
}
/// <summary>
/// DTO for creating a company admin user (SuperAdmin only)
/// </summary>
public class CreateCompanyAdminDto
{
public int CompanyId { get; set; }
[Required]
public string CompanyName { get; set; } = string.Empty;
[Required(ErrorMessage = "First name is required")]
[StringLength(100, ErrorMessage = "First name cannot exceed 100 characters")]
[Display(Name = "First Name")]
public string FirstName { get; set; } = string.Empty;
[Required(ErrorMessage = "Last name is required")]
[StringLength(100, ErrorMessage = "Last name cannot exceed 100 characters")]
[Display(Name = "Last Name")]
public string LastName { get; set; } = string.Empty;
[Required(ErrorMessage = "Email is required")]
[EmailAddress(ErrorMessage = "Invalid email address")]
[Display(Name = "Email")]
public string Email { get; set; } = string.Empty;
[Required(ErrorMessage = "Password is required")]
[StringLength(100, MinimumLength = 8, ErrorMessage = "Password must be at least 8 characters long")]
[RegularExpression(@"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&#^()_+=\-\[\]{};':""\\|,.<>\/`~])[A-Za-z\d@$!%*?&#^()_+=\-\[\]{};':""\\|,.<>\/`~]{8,}$",
ErrorMessage = "Password must contain at least one uppercase letter, one lowercase letter, one digit, and one special character")]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; } = string.Empty;
[Phone(ErrorMessage = "Invalid phone number")]
[Display(Name = "Phone")]
public string? Phone { get; set; }
[StringLength(100, ErrorMessage = "Department cannot exceed 100 characters")]
[Display(Name = "Department")]
public string? Department { get; set; }
[StringLength(100, ErrorMessage = "Position cannot exceed 100 characters")]
[Display(Name = "Position")]
public string? Position { get; set; }
}
/// <summary>
/// Simplified company info DTO for PDF generation (avoids circular dependencies)
/// </summary>
public class CompanyInfoDto
{
public string CompanyName { get; set; } = string.Empty;
public string? Phone { get; set; }
public string? Address { get; set; }
public string? City { get; set; }
public string? State { get; set; }
public string? ZipCode { get; set; }
public string? PrimaryContactEmail { get; set; }
}