d5ad9fa073
- 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>
89 lines
3.9 KiB
C#
89 lines
3.9 KiB
C#
using System.ComponentModel.DataAnnotations;
|
|
using PowderCoating.Core.Enums;
|
|
|
|
namespace PowderCoating.Application.DTOs.Kiosk;
|
|
|
|
// ── Staff-facing ──────────────────────────────────────────────────────────────
|
|
|
|
/// <summary>Input for sending a remote intake link to a customer by email.</summary>
|
|
public class SendRemoteLinkDto
|
|
{
|
|
[Required, EmailAddress]
|
|
public string Email { get; set; } = string.Empty;
|
|
|
|
/// <summary>Optional — used to personalise the email greeting.</summary>
|
|
public string? CustomerName { get; set; }
|
|
}
|
|
|
|
// ── Customer-facing step DTOs ─────────────────────────────────────────────────
|
|
|
|
/// <summary>Step 1 — Contact information submitted by the customer.</summary>
|
|
public class SubmitKioskContactDto
|
|
{
|
|
[Required, MaxLength(100)]
|
|
public string FirstName { get; set; } = string.Empty;
|
|
|
|
[Required, MaxLength(100)]
|
|
public string LastName { get; set; } = string.Empty;
|
|
|
|
[Required, Phone]
|
|
public string Phone { get; set; } = string.Empty;
|
|
|
|
[Required, EmailAddress]
|
|
public string Email { get; set; } = string.Empty;
|
|
|
|
public bool IsReturningCustomer { get; set; }
|
|
}
|
|
|
|
/// <summary>Step 2 — Job description submitted by the customer.</summary>
|
|
public class SubmitKioskJobDto
|
|
{
|
|
[Required, MaxLength(2000)]
|
|
public string JobDescription { get; set; } = string.Empty;
|
|
|
|
public string? HowDidYouHearAboutUs { get; set; }
|
|
}
|
|
|
|
/// <summary>Step 3 — Terms agreement (+ optional drawn signature for in-person sessions).</summary>
|
|
public class SubmitKioskTermsDto
|
|
{
|
|
[Required]
|
|
[Range(typeof(bool), "true", "true", ErrorMessage = "You must agree to the terms to continue.")]
|
|
public bool AgreedToTerms { get; set; }
|
|
|
|
public bool SmsOptIn { get; set; }
|
|
|
|
/// <summary>Base-64 PNG from signature_pad; required for InPerson sessions, null for Remote.</summary>
|
|
public string? SignatureDataBase64 { get; set; }
|
|
}
|
|
|
|
// ── Staff review list ─────────────────────────────────────────────────────────
|
|
|
|
/// <summary>One row in the Kiosk Intakes staff review list.</summary>
|
|
public class KioskSessionListDto
|
|
{
|
|
public int Id { get; set; }
|
|
public Guid SessionToken { get; set; }
|
|
public KioskSessionType SessionType { get; set; }
|
|
public KioskSessionStatus Status { get; set; }
|
|
public string CustomerFirstName { get; set; } = string.Empty;
|
|
public string CustomerLastName { get; set; } = string.Empty;
|
|
public string CustomerEmail { get; set; } = string.Empty;
|
|
public string CustomerPhone { get; set; } = string.Empty;
|
|
public string JobDescription { get; set; } = string.Empty;
|
|
public bool SmsOptIn { get; set; }
|
|
public DateTime? SubmittedAt { get; set; }
|
|
public DateTime ExpiresAt { get; set; }
|
|
public int? LinkedCustomerId { get; set; }
|
|
public int? LinkedJobId { get; set; }
|
|
public int? LinkedQuoteId { get; set; }
|
|
public string? RemoteLinkEmail { get; set; }
|
|
|
|
public string CustomerFullName => $"{CustomerFirstName} {CustomerLastName}".Trim();
|
|
public string JobDescriptionSnippet =>
|
|
JobDescription.Length > 80 ? JobDescription[..80] + "…" : JobDescription;
|
|
public bool IsConverted => LinkedJobId.HasValue || LinkedQuoteId.HasValue;
|
|
public bool IsExpired => Status == KioskSessionStatus.Expired ||
|
|
(Status == KioskSessionStatus.Active && DateTime.UtcNow > ExpiresAt);
|
|
}
|