- 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>
- Customer entity + DTO: new BillingEmail field (accounting/invoicing address)
- Email fields now accept comma-separated lists; DTO validates each address individually
- NotificationService: SendToEmailListAsync helper fans out to all addresses in a list;
NotifyQuoteSentAsync accepts optional overrideEmail so staff can send to an ad-hoc address
- Migration: AddCustomerBillingEmail
- Customer Create/Edit/Details views updated to show Billing Email field
- customer-billing-email.js: client-side helpers for billing email input
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
QuoteDeclinedByCustomer was used for both approve and decline responses,
so approval notifications showed the wrong type in the log. Added a distinct
QuoteApprovedByCustomer = 16 enum value, wired up the correct type in
NotificationService, added default templates in both the service fallback
dictionary and SeedData, and updated placeholder hints in CompanySettings.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a customizable QuoteSent SMS template to seed data and
DefaultTemplates so companies can edit the quote approval message
from Notification Templates. Wires NotifyQuoteSentSmsAsync to use
the template system instead of a hardcoded string. Updates
SmsConsentConfirmation wording to mention quote approvals alongside
job updates. Help docs and AI knowledge base updated to match.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add 'Send Quote via SMS' button on quote details page that sends the approval
link to the customer via SMS (respects NotifyBySms, handles prospects via ProspectPhone)
- Reuses existing valid approval token rather than regenerating, so a previously
emailed link stays valid when SMS is also sent
- Fix Twilio appsettings.json placeholders (real credentials moved to gitignored
appsettings.Development.json)
- Fix passkey login ignoring ReturnUrl: biometric login on the login page now
respects the form's ReturnUrl hidden field so QR-code and deep-link flows
redirect correctly after authentication instead of always going to the dashboard
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Three-tier SMS gate: platform kill-switch → admin force-disable → plan AllowSms → company opt-in
- CompanySmsAgreement entity records admin acceptance of TCPA terms with IP, user agent, and terms version
- SMS terms of service modal on Company Settings with versioned re-agreement (AppConstants.SmsTermsVersion)
- Dev redirect: non-production SMS routed to Twilio:DevRedirectPhone to protect real customer numbers
- Removed redundant Ready for Pickup SMS (Job Completed covers it)
- Role-based compose modal on job completion: Admin/Manager reviews and edits before send; ShopFloor auto-sends
- Send SMS button on job details for ad-hoc messages (Admin/Manager only)
- SendJobSmsAsync auto-appends STOP opt-out language if missing
- Migrations: AddSmsGating, AddCompanySmsAgreement
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>