726bebdce9
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>
238 lines
8.9 KiB
C#
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; }
|
|
}
|