Files
PowderCoatingLogix/src/PowderCoating.Core/Entities/KioskSession.cs
T
spouliot d5ad9fa073 Add KioskIntakeOutput company setting and fix kiosk submission bugs
- New CompanyPreferences.KioskIntakeOutput setting ("Quote" default / "Job"): controls
  what the kiosk creates on submission; shown as a card-style radio toggle in
  Company Settings → Kiosk tab
- KioskSession.LinkedQuoteId added so quote-first sessions link back to the draft quote
- Migration AddKioskIntakeOutputSetting applies both schema changes
- ProcessSubmissionAsync branches on setting: creates Draft quote (quote-first) or
  Pending job (job-first); save order fixed (CompleteAsync before using DB-assigned Id as FK)
- Terms.cshtml pricing paragraph is now dynamic: "subject to formal quote" for Quote mode,
  "team member will reach out about pricing" for Job mode
- Customer Intakes list: "View Quote" button appears when LinkedQuoteId is set
- Notification label fixed: Remote sessions now say "Remote Intake", not "Walk-in Intake"
- Inactivity reset shortened to 45 s on intake steps
- Signature pad: hosted locally (no CDN), canvas resize deferred via requestAnimationFrame
- AI photo upload: client-side compression to ≤1200px + AbortController 120 s timeout
- Help article and AI knowledge base updated with kiosk feature

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

55 lines
3.5 KiB
C#

using PowderCoating.Core.Enums;
namespace PowderCoating.Core.Entities;
/// <summary>
/// Represents one customer self-service intake session — either completed on the front-desk tablet
/// (InPerson) or via an emailed link the customer fills out on their own device (Remote).
/// Sessions are tenant-scoped and soft-deletable. Load anonymous sessions with ignoreQueryFilters:true.
/// </summary>
public class KioskSession : BaseEntity
{
/// <summary>URL-safe GUID used in all kiosk routes; unique across the table.</summary>
public Guid SessionToken { get; set; } = Guid.NewGuid();
public KioskSessionType SessionType { get; set; }
public KioskSessionStatus Status { get; set; } = KioskSessionStatus.Active;
// ── Step 1 — Contact ─────────────────────────────────────────────────────
public string CustomerFirstName { get; set; } = string.Empty;
public string CustomerLastName { get; set; } = string.Empty;
public string CustomerPhone { get; set; } = string.Empty;
public string CustomerEmail { get; set; } = string.Empty;
public bool IsReturningCustomer { get; set; }
// ── Step 2 — Job Description ──────────────────────────────────────────────
public string JobDescription { get; set; } = string.Empty;
public string? HowDidYouHearAboutUs { get; set; }
// ── Step 3 — Terms & Consent ──────────────────────────────────────────────
public bool AgreedToTerms { get; set; }
public DateTime? AgreedToTermsAt { get; set; }
/// <summary>Customer opted in to SMS order updates; sets Customer.NotifyBySms on submission.</summary>
public bool SmsOptIn { get; set; }
/// <summary>Base-64 PNG from signature_pad; null for Remote sessions (no drawn signature required).</summary>
public string? SignatureDataBase64 { get; set; }
// ── Outcome ───────────────────────────────────────────────────────────────
public int? LinkedCustomerId { get; set; }
/// <summary>Set when KioskIntakeOutput = "Job". Null when a Quote was created instead.</summary>
public int? LinkedJobId { get; set; }
/// <summary>Set when KioskIntakeOutput = "Quote". Null when a Job was created instead.</summary>
public int? LinkedQuoteId { get; set; }
public DateTime? SubmittedAt { get; set; }
/// <summary>Sessions auto-expire 2 h after creation (InPerson) or 48 h (Remote). ExpiresAt is set at creation.</summary>
public DateTime ExpiresAt { get; set; }
// ── Remote-only ───────────────────────────────────────────────────────────
public string? RemoteLinkEmail { get; set; }
public DateTime? RemoteLinkSentAt { get; set; }
// ── Navigation ────────────────────────────────────────────────────────────
public virtual Customer? LinkedCustomer { get; set; }
public virtual Job? LinkedJob { get; set; }
}