Files
PowderCoatingLogix/src/PowderCoating.Core/Entities/Appointment.cs
T
spouliot 2bf8871892 Fix NoExtraLayerCharge persistence, appointment reminders, coat notes display, scroll restoration, and invoice Send dead-button
- 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>
2026-05-19 15:48:16 -04:00

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; }
}