2bf8871892
- Appointment reminders: add AppointmentReminderBackgroundService (60s poll), ReminderSentAt dedup stamp, NotifyAppointmentReminderAsync sends both customer email and creator staff email; AppointmentReminderStaff notification type + default template added; DateTime.Now used instead of UtcNow to match locally-stored ScheduledStartTime; ToLocalTime() double-conversion removed - NoExtraLayerCharge not persisted: flag existed on CreateQuoteItemCoatDto and was used by pricing engine but never written to JobItemCoat/QuoteItemCoat entities — every edit reset it to false and re-applied the extra layer charge; added column to both entities (migration AddNoExtraLayerChargeToCoats), both read DTOs, all 3 JobItemAssemblyService overloads, JobItemCoatSeed inner class, and existingItemsData JSON in all 5 wizard views; fixed JS template path that hard-coded noExtraLayerCharge: false - Coat notes not visible: notes were rendered in desktop job details but missing from the wizard item card summary and the mobile card view; both fixed - Scroll position lost on item save: sessionStorage save/restore added to item-wizard.js owner form submit handler; path-keyed so cross-page navigation does not restore stale position; requestAnimationFrame used for reliable mobile scroll restoration - Invoice Send dead button: #sendChannelModal was gated inside @if (isDraft) but the button targeting it fires for Sent/Overdue invoices too when customer has both email and SMS; modal moved outside the Draft guard - InitialCreate migration added for fresh database installs; Baseline migration guarded with IF OBJECT_ID check so it no-ops on fresh DBs; Razor scoping bug fixed in Customers/Index.cshtml Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
111 lines
3.5 KiB
C#
111 lines
3.5 KiB
C#
namespace PowderCoating.Core.Entities;
|
|
|
|
/// <summary>
|
|
/// Represents a scheduled appointment for customer drop-offs, pick-ups, consultations, or job work.
|
|
/// </summary>
|
|
public class Appointment : BaseEntity
|
|
{
|
|
/// <summary>
|
|
/// Auto-generated appointment number in format APT-YYMM-####
|
|
/// </summary>
|
|
public string AppointmentNumber { get; set; } = string.Empty;
|
|
|
|
/// <summary>
|
|
/// Brief title/description of the appointment
|
|
/// </summary>
|
|
public string Title { get; set; } = string.Empty;
|
|
|
|
/// <summary>
|
|
/// Detailed description and notes about the appointment
|
|
/// </summary>
|
|
public string? Description { get; set; }
|
|
|
|
// Customer Information
|
|
/// <summary>
|
|
/// Optional foreign key to Customer (not required for internal appointments like employee days off)
|
|
/// </summary>
|
|
public int? CustomerId { get; set; }
|
|
|
|
/// <summary>
|
|
/// Optional foreign key to Job (required for JOB_WORK appointment type)
|
|
/// </summary>
|
|
public int? JobId { get; set; }
|
|
|
|
// Lookup Foreign Keys
|
|
/// <summary>
|
|
/// Foreign key to AppointmentStatusLookup
|
|
/// </summary>
|
|
public int AppointmentStatusId { get; set; }
|
|
|
|
/// <summary>
|
|
/// Foreign key to AppointmentTypeLookup
|
|
/// </summary>
|
|
public int AppointmentTypeId { get; set; }
|
|
|
|
/// <summary>
|
|
/// Optional foreign key to ApplicationUser assigned to handle this appointment
|
|
/// </summary>
|
|
public string? AssignedUserId { get; set; }
|
|
|
|
// Timing
|
|
/// <summary>
|
|
/// Scheduled start date and time (UTC)
|
|
/// </summary>
|
|
public DateTime ScheduledStartTime { get; set; }
|
|
|
|
/// <summary>
|
|
/// Scheduled end date and time (UTC)
|
|
/// </summary>
|
|
public DateTime ScheduledEndTime { get; set; }
|
|
|
|
/// <summary>
|
|
/// Whether this is an all-day appointment (hides specific times)
|
|
/// </summary>
|
|
public bool IsAllDay { get; set; } = false;
|
|
|
|
/// <summary>
|
|
/// Actual start time when customer arrived (UTC, nullable until appointment starts)
|
|
/// </summary>
|
|
public DateTime? ActualStartTime { get; set; }
|
|
|
|
/// <summary>
|
|
/// Actual end time when appointment finished (UTC, nullable until appointment completes)
|
|
/// </summary>
|
|
public DateTime? ActualEndTime { get; set; }
|
|
|
|
// Additional Information
|
|
/// <summary>
|
|
/// Optional location at the shop (e.g., "Main Office", "Loading Dock", "Inspection Area")
|
|
/// </summary>
|
|
public string? Location { get; set; }
|
|
|
|
/// <summary>
|
|
/// Internal notes for staff (not visible to customer)
|
|
/// </summary>
|
|
public string? Notes { get; set; }
|
|
|
|
// Reminder Settings
|
|
/// <summary>
|
|
/// Whether to send reminder notifications
|
|
/// </summary>
|
|
public bool IsReminderEnabled { get; set; } = true;
|
|
|
|
/// <summary>
|
|
/// How many minutes before appointment to send reminder (default 30 minutes)
|
|
/// </summary>
|
|
public int ReminderMinutesBefore { get; set; } = 30;
|
|
|
|
/// <summary>
|
|
/// UTC timestamp when the reminder was dispatched. Null means it hasn't fired yet.
|
|
/// The background service uses this as a deduplication guard to prevent double-sending.
|
|
/// </summary>
|
|
public DateTime? ReminderSentAt { get; set; }
|
|
|
|
// Navigation Properties
|
|
public virtual Customer? Customer { get; set; }
|
|
public virtual Job? Job { get; set; }
|
|
public virtual AppointmentStatusLookup AppointmentStatus { get; set; } = null!;
|
|
public virtual AppointmentTypeLookup AppointmentType { get; set; } = null!;
|
|
public virtual ApplicationUser? AssignedUser { get; set; }
|
|
}
|