Files
PowderCoatingLogix/src/PowderCoating.Application/Interfaces/INotificationService.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

102 lines
4.7 KiB
C#

using PowderCoating.Core.Entities;
namespace PowderCoating.Application.Interfaces;
public interface INotificationService
{
/// <summary>
/// Notify when a quote is created/sent. Handles both registered customers and prospects.
/// Optionally attaches the quote PDF to the email.
/// </summary>
Task NotifyQuoteSentAsync(Quote quote, byte[]? pdfAttachment = null, string? pdfFilename = null, string? overrideEmail = null);
/// <summary>
/// Sends the quote approval link to the customer via SMS.
/// Handles both registered customers (respects NotifyBySms) and prospects (ProspectPhone).
/// Returns (success, errorMessage) so the caller can surface the result to the user.
/// </summary>
Task<(bool Success, string? Error)> NotifyQuoteSentSmsAsync(Quote quote);
/// <summary>
/// Notify when a quote is approved by a customer.
/// </summary>
Task NotifyQuoteApprovedAsync(Quote quote);
/// <summary>
/// Notify customer of a job status change. Also sends SMS when status is READY_FOR_PICKUP.
/// </summary>
Task NotifyJobStatusChangedAsync(Job job, string newStatusCode, string newStatusDisplayName);
/// <summary>
/// Notify customer when a job is completed and ready for pickup.
/// When <paramref name="suppressSms"/> is true the SMS is skipped so an admin can review
/// the message via <see cref="RenderJobCompletedSmsAsync"/> before sending manually.
/// </summary>
Task NotifyJobCompletedAsync(Job job, bool suppressSms = false);
/// <summary>
/// Renders the job-completed SMS text for admin preview without sending it.
/// Returns null when SMS is not allowed for the company or the customer has not opted in.
/// </summary>
Task<string?> RenderJobCompletedSmsAsync(Job job);
/// <summary>
/// Sends a manually-composed SMS for a job (Admin/Manager compose-before-send path).
/// Appends "Reply STOP to opt out." if not already present, sends, and writes a NotificationLog row.
/// Returns (success, errorMessage).
/// </summary>
Task<(bool Success, string? Error)> SendJobSmsAsync(Job job, string message);
/// <summary>
/// Sends a welcome/confirmation SMS after staff records verbal SMS consent.
/// This message confirms enrollment and provides opt-out instructions per CTIA guidelines.
/// </summary>
Task NotifySmsConsentGrantedAsync(Customer customer);
/// <summary>
/// Notify customer when an invoice has been sent.
/// Optionally includes an online payment link in the email body.
/// </summary>
Task NotifyInvoiceSentAsync(Invoice invoice, byte[]? pdfAttachment = null, string? pdfFilename = null, string? paymentUrl = null, string? overrideEmail = null, bool sendSms = false, string? viewUrl = null);
/// <summary>
/// Notify customer (internal) when a payment has been recorded on an invoice.
/// </summary>
Task NotifyPaymentReceivedAsync(Invoice invoice, Payment payment);
/// <summary>
/// Notify the company (internal) when a customer approves or declines a quote via the self-service portal.
/// </summary>
Task NotifyQuoteActedByCustomerAsync(Quote quote, bool approved, string? declineReason);
/// <summary>
/// Send a payment reminder to the customer for an overdue invoice.
/// </summary>
/// <param name="invoice">The overdue invoice (must have Customer loaded or CustomerId set).</param>
/// <param name="daysOverdue">Number of days since the invoice was due.</param>
Task NotifyPaymentReminderAsync(Invoice invoice, int daysOverdue);
/// <summary>
/// Send an online payment receipt to the customer after a successful Stripe payment.
/// </summary>
Task NotifyOnlinePaymentReceivedAsync(Invoice invoice, decimal amountPaid, decimal surchargePaid, string paymentIntentId);
/// <summary>
/// Notify the company (internal) when a customer pays a deposit online for a quote.
/// </summary>
Task NotifyDepositReceivedAsync(Quote quote, decimal amountPaid, decimal surchargePaid, string paymentIntentId);
/// <summary>
/// Alert company staff when a Stripe chargeback (dispute) is opened on an invoice payment.
/// </summary>
Task NotifyChargebackAlertAsync(Invoice invoice, string disputeId, decimal amount, string reason);
/// <summary>
/// Sends an appointment reminder email to the linked customer (if opted in) and writes a
/// notification log row. Called by <see cref="PowderCoating.Web.BackgroundServices.AppointmentReminderBackgroundService"/>
/// when the reminder window opens. In-app bell notification is handled by the caller.
/// </summary>
Task NotifyAppointmentReminderAsync(Appointment appointment);
}