97745f9a65
Settings tab (Company Settings > Timeclock): - Enable/disable timeclock toggle (hides nav link and attendance report when off) - Allow multiple clock-ins per day toggle - Auto clock-out after X hours (auto-closes forgotten open entries on next punch) - Kiosk devices table: lists activated tablets with name, activated date, last seen; Deactivate button removes that device's access immediately Multi-kiosk support (replaces single TimeclockKioskToken on Company): - New TimeclockKioskDevice entity (one row per tablet, unique token, DeviceName, LastSeenAt) - KioskActivate GET shows a form for optional device name before activating - KioskDeactivate POST accepts device ID, deletes specific row (not all devices) - Kiosk validation (Kiosk, KioskEmployees, KioskPunch) queries device table with ignoreQueryFilters since no user is logged in on kiosk requests - LastSeenAt updated on each Kiosk page load Enforcement: - ClockIn and KioskPunch both auto-close stale entries if AutoClockOutHours is set - ClockIn and KioskPunch both block second same-day punch if AllowMultiplePunches=false - TimeclockEnabled=false hides nav link (SubscriptionMiddleware sets Items key) and returns Forbid on kiosk punch - Migration: AddTimeclockSettings (adds 3 columns to Companies, new TimeclockKioskDevices table) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
157 lines
8.1 KiB
C#
157 lines
8.1 KiB
C#
using PowderCoating.Core.Enums;
|
|
|
|
namespace PowderCoating.Core.Entities;
|
|
|
|
/// <summary>
|
|
/// Represents a company/tenant in the multi-tenant system
|
|
/// </summary>
|
|
public class Company : BaseEntity
|
|
{
|
|
// Basic Information
|
|
public string CompanyName { get; set; } = string.Empty;
|
|
public string? CompanyCode { get; set; } // Short code (e.g., ABC)
|
|
|
|
// Contact Information
|
|
public string PrimaryContactName { get; set; } = string.Empty;
|
|
public string PrimaryContactEmail { get; set; } = string.Empty;
|
|
public string? Phone { get; set; }
|
|
|
|
// Address
|
|
public string? Address { get; set; }
|
|
public string? City { get; set; }
|
|
public string? State { get; set; }
|
|
public string? ZipCode { get; set; }
|
|
|
|
// Subscription/Status
|
|
public bool IsActive { get; set; } = true;
|
|
public DateTime SubscriptionStartDate { get; set; } = DateTime.UtcNow;
|
|
public DateTime? SubscriptionEndDate { get; set; }
|
|
public int SubscriptionPlan { get; set; } = 0;
|
|
public SubscriptionStatus SubscriptionStatus { get; set; } = SubscriptionStatus.Active;
|
|
public string? StripeCustomerId { get; set; }
|
|
public string? StripeSubscriptionId { get; set; }
|
|
|
|
/// <summary>
|
|
/// When true the company has complimentary/internal access.
|
|
/// All plan limits are treated as unlimited and the subscription
|
|
/// expiry banner/lockout is suppressed regardless of SubscriptionEndDate.
|
|
/// </summary>
|
|
public bool IsComped { get; set; } = false;
|
|
|
|
// Stripe Connect — online invoice payments
|
|
public string? StripeAccountId { get; set; } // acct_xxx from OAuth
|
|
public StripeConnectStatus StripeConnectStatus { get; set; } = StripeConnectStatus.NotConnected;
|
|
public OnlinePaymentSurchargeType OnlinePaymentSurchargeType { get; set; } = OnlinePaymentSurchargeType.None;
|
|
public decimal OnlinePaymentSurchargeValue { get; set; } = 0; // % or flat $ depending on type
|
|
public bool OnlineSurchargeAcknowledged { get; set; } = false; // shop accepted compliance disclaimer
|
|
|
|
/// <summary>Internal notes about manual subscription changes (not shown to the company).</summary>
|
|
public string? SubscriptionNotes { get; set; }
|
|
|
|
// Per-company limit overrides. null = use plan config default. -1 = unlimited.
|
|
public int? MaxUsersOverride { get; set; }
|
|
public int? MaxActiveJobsOverride { get; set; }
|
|
public int? MaxCustomersOverride { get; set; }
|
|
public int? MaxQuotesOverride { get; set; }
|
|
public int? MaxCatalogItemsOverride { get; set; }
|
|
public int? MaxJobPhotosOverride { get; set; }
|
|
public int? MaxQuotePhotosOverride { get; set; }
|
|
/// <summary>null = use plan config default. -1 = unlimited. 0 = disabled for this company.</summary>
|
|
public int? MaxAiPhotoQuotesPerMonthOverride { get; set; }
|
|
|
|
// AI Feature Flags (SuperAdmin-controlled per company)
|
|
/// <summary>Enables/disables AI Photo Quote analysis for this company.</summary>
|
|
public bool AiPhotoQuotesEnabled { get; set; } = true;
|
|
/// <summary>Enables/disables the AI Inventory Assist lookup for this company.</summary>
|
|
public bool AiInventoryAssistEnabled { get; set; } = true;
|
|
/// <summary>Enables/disables the AI Catalog Price Check for this company.</summary>
|
|
public bool AiCatalogPriceCheckEnabled { get; set; } = true;
|
|
|
|
/// <summary>
|
|
/// Stores the billing period the customer selected at registration (or last changed on the Billing page).
|
|
/// true = annual billing, false = monthly billing.
|
|
/// Used when redirecting to Stripe Checkout at trial-end so the right price ID is used automatically.
|
|
/// </summary>
|
|
public bool IsAnnualBilling { get; set; } = false;
|
|
|
|
// Per-company feature overrides (SuperAdmin-controlled)
|
|
/// <summary>
|
|
/// null = use plan config default.
|
|
/// true = force-enable online payments for this company regardless of plan.
|
|
/// false = force-disable online payments for this company regardless of plan.
|
|
/// </summary>
|
|
public bool? OnlinePaymentsOverride { get; set; }
|
|
/// <summary>
|
|
/// null = use plan config default.
|
|
/// true = force-enable accounting module for this company regardless of plan.
|
|
/// false = force-disable accounting module for this company regardless of plan.
|
|
/// </summary>
|
|
public bool? AccountingOverride { get; set; }
|
|
|
|
/// <summary>
|
|
/// Company admin opt-in for SMS notifications. Defaults to false — company admin must
|
|
/// explicitly accept the SMS terms of service before enabling. Has no effect if the plan
|
|
/// does not allow SMS or if SmsDisabledByAdmin is true.
|
|
/// </summary>
|
|
public bool SmsEnabled { get; set; } = false;
|
|
|
|
/// <summary>
|
|
/// SuperAdmin force-disable for this company's SMS. When true, no SMS is sent regardless
|
|
/// of plan or company settings. Use when a company is abusing SMS or requests a full opt-out.
|
|
/// </summary>
|
|
public bool SmsDisabledByAdmin { get; set; } = false;
|
|
|
|
// Email marketing opt-out (CAN-SPAM compliance for platform broadcast emails)
|
|
public bool MarketingEmailOptOut { get; set; } = false;
|
|
public string MarketingUnsubscribeToken { get; set; } = Guid.NewGuid().ToString("N");
|
|
|
|
/// <summary>
|
|
/// Determines whether financial reports (P&L, Balance Sheet, Cash Flow) use
|
|
/// cash-basis or accrual-basis presentation. Switchable at any time — no GL
|
|
/// re-posting occurs. Default is Accrual (standard for most businesses).
|
|
/// </summary>
|
|
public AccountingMethod AccountingMethod { get; set; } = AccountingMethod.Accrual;
|
|
|
|
/// <summary>
|
|
/// When set, prevents creating or editing accounting entries (JEs, bills, expenses) with dates
|
|
/// on or before this date. Protects closed periods from accidental backdating. Null = no lock.
|
|
/// </summary>
|
|
public DateTime? BookLockedThrough { get; set; }
|
|
|
|
// Settings
|
|
public string? TimeZone { get; set; } = "America/New_York";
|
|
public byte[]? LogoData { get; set; } // Legacy - kept for backward compatibility
|
|
public string? LogoContentType { get; set; } // Legacy - kept for backward compatibility
|
|
public string? LogoFilePath { get; set; } // Filesystem path: /media/{CompanyId}/company-logo.{ext}
|
|
|
|
// Kiosk
|
|
/// <summary>
|
|
/// Random token written to a long-lived HttpOnly cookie on the front-desk tablet when the
|
|
/// owner activates the kiosk. Kiosk routes validate this token against the cookie so the
|
|
/// tablet can serve the intake form without requiring a logged-in user.
|
|
/// Null = kiosk not activated. Regenerate to revoke the current device.
|
|
/// </summary>
|
|
public string? KioskActivationToken { get; set; }
|
|
|
|
/// <summary>Timeclock feature enabled for this company. When false, the nav link, dashboard, and reports are hidden.</summary>
|
|
public bool TimeclockEnabled { get; set; } = true;
|
|
|
|
/// <summary>When true, employees can clock in/out multiple times per day (lunch breaks, etc.). When false, only one in/out pair is allowed per day.</summary>
|
|
public bool TimeclockAllowMultiplePunchesPerDay { get; set; } = true;
|
|
|
|
/// <summary>If set, any open clock entry older than this many hours is automatically closed on the next clock-in. Null = no auto clock-out.</summary>
|
|
public int? TimeclockAutoClockOutHours { get; set; }
|
|
|
|
// Navigation Properties
|
|
public virtual ICollection<ApplicationUser> Users { get; set; } = new List<ApplicationUser>();
|
|
public virtual ICollection<Customer> Customers { get; set; } = new List<Customer>();
|
|
public virtual ICollection<Job> Jobs { get; set; } = new List<Job>();
|
|
public virtual ICollection<Equipment> Equipment { get; set; } = new List<Equipment>();
|
|
public virtual ICollection<Quote> Quotes { get; set; } = new List<Quote>();
|
|
public virtual ICollection<InventoryItem> InventoryItems { get; set; } = new List<InventoryItem>();
|
|
public virtual ICollection<Vendor> Vendors { get; set; } = new List<Vendor>();
|
|
public virtual ICollection<PricingTier> PricingTiers { get; set; } = new List<PricingTier>();
|
|
public virtual CompanyOperatingCosts? OperatingCosts { get; set; }
|
|
public virtual CompanyPreferences? Preferences { get; set; }
|
|
}
|