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>
This commit is contained in:
@@ -82,6 +82,21 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const ownerForm = hfc?.closest('form');
|
||||
if (ownerForm) {
|
||||
ownerForm.addEventListener('submit', writeHiddenFields, { capture: true });
|
||||
|
||||
// Save scroll position before the form causes a full-page reload so we can
|
||||
// restore it after the server redirects back to this page. Key is path-specific
|
||||
// so navigating away and back doesn't restore a stale position.
|
||||
const scrollKey = 'wizardScrollY:' + location.pathname;
|
||||
ownerForm.addEventListener('submit', () => {
|
||||
sessionStorage.setItem(scrollKey, String(Math.round(window.scrollY)));
|
||||
}, { capture: true });
|
||||
|
||||
// Restore on load — fire after layout is painted so scrollTo lands correctly.
|
||||
const savedY = sessionStorage.getItem(scrollKey);
|
||||
if (savedY !== null) {
|
||||
sessionStorage.removeItem(scrollKey);
|
||||
requestAnimationFrame(() => window.scrollTo({ top: parseInt(savedY, 10), behavior: 'instant' }));
|
||||
}
|
||||
}
|
||||
|
||||
// Close any open powder combobox or catalog lookup dropdown when clicking outside it
|
||||
@@ -2731,8 +2746,9 @@ function buildCardHtml(item, i) {
|
||||
const orderBadge = (!c.inventoryItemId && c.powderToOrder)
|
||||
? ` <span class="badge bg-warning text-dark" style="font-size:.65em;vertical-align:middle;" title="Custom powder — must be purchased before coating"><i class="bi bi-cart me-1"></i>ORDER ${parseFloat(c.powderToOrder).toFixed(2)} lbs</span>`
|
||||
: '';
|
||||
const coatNotes = c.notes ? `<span class="fst-italic ms-1 opacity-75">— ${escHtml(c.notes)}</span>` : '';
|
||||
return `<div style="font-size:.8rem;" class="text-muted mt-1">
|
||||
<i class="bi bi-layers me-1"></i><span class="fw-semibold">${escHtml(c.coatName || 'Coat')}</span>${color ? ` — ${color}${code}` : ''}${orderBadge}
|
||||
<i class="bi bi-layers me-1"></i><span class="fw-semibold">${escHtml(c.coatName || 'Coat')}</span>${color ? ` — ${color}${code}` : ''}${orderBadge}${coatNotes}
|
||||
</div>`;
|
||||
}).join('');
|
||||
|
||||
@@ -3414,7 +3430,7 @@ function loadItemsFromTemplate(templateItems) {
|
||||
coverageSqFtPerLb: c.coverageSqFtPerLb || 30,
|
||||
transferEfficiency: c.transferEfficiency || 65,
|
||||
powderCostPerLb: c.powderCostPerLb || null,
|
||||
noExtraLayerCharge: false
|
||||
noExtraLayerCharge: !!c.noExtraLayerCharge
|
||||
})),
|
||||
prepServices: (ti.prepServices || []).map(p => ({
|
||||
prepServiceId: p.prepServiceId,
|
||||
|
||||
Reference in New Issue
Block a user