From a0bdd2b5b49882668645bc9c8c363e24373146ce Mon Sep 17 00:00:00 2001
From: Scott Pouliot
Date: Wed, 20 May 2026 21:37:10 -0400
Subject: [PATCH] Sweep all .cshtml files for encoding corruption; add
pre-commit guard
Replace all corruption variants with HTML entities across 226 view files:
- 3-char UTF-8-as-Win1252 sequences (ae-corruption)
- Standalone smart/curly quotes that break C# Razor expressions
- Partially re-corrupted variants where the 3rd byte was normalised to ASCII
tools/Fix-Encoding.ps1: re-runnable sweep; uses [char] code points so the
script itself never contains a literal non-ASCII character; supports -DryRun
.githooks/pre-commit: blocks commits containing the ae-corruption byte
signature (xc3xa2xe2x82xac); git core.hooksPath = .githooks so the
hook is repo-committed and active for all future work on this machine.
Build clean; 225 unit tests pass.
Co-Authored-By: Claude Sonnet 4.6
---
.githooks/pre-commit | 27 +++
.../Views/AccountDataExport/Index.cshtml | 6 +-
.../Views/AccountingExport/Index.cshtml | 6 +-
.../Views/Accounts/Create.cshtml | 16 +-
.../Views/Accounts/Edit.cshtml | 10 +-
.../Views/Accounts/Index.cshtml | 8 +-
.../Views/Accounts/Ledger.cshtml | 8 +-
.../Views/Accounts/YearEndClose.cshtml | 6 +-
.../Views/AiUsageReport/Index.cshtml | 24 +--
.../Views/Announcements/Index.cshtml | 4 +-
.../Announcements/_AnnouncementForm.cshtml | 10 +-
.../Views/Appointments/Create.cshtml | 6 +-
.../Views/Appointments/Details.cshtml | 4 +-
.../Views/Appointments/Edit.cshtml | 4 +-
.../Views/AuditLog/Details.cshtml | 12 +-
.../Views/AuditLog/Index.cshtml | 6 +-
.../Views/BankReconciliations/Create.cshtml | 2 +-
.../BankReconciliations/Reconcile.cshtml | 8 +-
.../Views/BankReconciliations/Report.cshtml | 4 +-
.../Views/BannedIps/Index.cshtml | 2 +-
.../Views/Billing/Index.cshtml | 6 +-
.../Views/Bills/Create.cshtml | 34 ++--
.../Views/Bills/Details.cshtml | 12 +-
src/PowderCoating.Web/Views/Bills/Edit.cshtml | 18 +-
.../Views/Bills/Index.cshtml | 6 +-
.../Views/Bills/RecurringDetection.cshtml | 2 +-
.../Views/Budgets/Edit.cshtml | 4 +-
.../Views/Budgets/Index.cshtml | 2 +-
.../Views/BugReport/Index.cshtml | 4 +-
.../Views/BugReport/Submit.cshtml | 12 +-
.../Views/CatalogCategories/Create.cshtml | 2 +-
.../Views/CatalogCategories/Edit.cshtml | 2 +-
.../Views/CatalogItems/AiPriceCheck.cshtml | 20 +-
.../Views/CatalogItems/Create.cshtml | 14 +-
.../Views/CatalogItems/Edit.cshtml | 12 +-
.../Views/CatalogItems/Index.cshtml | 2 +-
.../Views/Companies/Details.cshtml | 38 ++--
.../Views/Companies/Edit.cshtml | 8 +-
.../Views/Companies/Index.cshtml | 8 +-
.../Views/CompanyHealth/Index.cshtml | 8 +-
.../CompanySettings/DeleteAccount.cshtml | 12 +-
.../Views/CompanySettings/EditTemplate.cshtml | 6 +-
.../Views/CompanySettings/Index.cshtml | 148 +++++++-------
.../CompanySettings/_LookupModals.cshtml | 16 +-
.../Views/CompanyUsers/Create.cshtml | 6 +-
.../Views/CompanyUsers/Edit.cshtml | 8 +-
.../Views/Contact/Index.cshtml | 4 +-
.../Views/Contact/Submissions.cshtml | 2 +-
.../Views/CreditMemos/Create.cshtml | 6 +-
.../Views/CreditMemos/Details.cshtml | 6 +-
.../Views/CreditMemos/Index.cshtml | 4 +-
.../Views/Customers/Activity.cshtml | 8 +-
.../Views/Customers/Create.cshtml | 10 +-
.../Views/Customers/Details.cshtml | 6 +-
.../Views/Customers/Edit.cshtml | 14 +-
.../Views/Customers/Index.cshtml | 8 +-
.../Views/Customers/Invoices.cshtml | 2 +-
.../Views/Customers/Statement.cshtml | 4 +-
.../Views/Dashboard/Index.cshtml | 54 ++---
.../Views/DashboardTips/Index.cshtml | 4 +-
.../Views/DataExport/Index.cshtml | 14 +-
.../Views/DataPurge/Index.cshtml | 26 +--
.../Views/Diagnostics/Index.cshtml | 14 +-
.../Views/EmailBroadcast/Preview.cshtml | 4 +-
.../EmailBroadcast/SelectCompanies.cshtml | 4 +-
.../Views/Equipment/Create.cshtml | 2 +-
.../Views/Equipment/Delete.cshtml | 8 +-
.../Views/Equipment/Details.cshtml | 8 +-
.../Views/Equipment/Index.cshtml | 4 +-
.../Views/Expenses/Details.cshtml | 4 +-
.../Views/FixedAssets/Create.cshtml | 8 +-
.../Views/FixedAssets/Details.cshtml | 8 +-
.../Views/FixedAssets/Edit.cshtml | 6 +-
.../Views/GiftCertificates/Create.cshtml | 2 +-
.../Views/GiftCertificates/Details.cshtml | 2 +-
.../Views/Help/AccountsPayable.cshtml | 54 ++---
.../Views/Help/CustomerIntakeKiosk.cshtml | 38 ++--
.../Views/Help/Customers.cshtml | 40 ++--
.../Views/Help/Equipment.cshtml | 44 ++--
.../Views/Help/GettingStarted.cshtml | 42 ++--
src/PowderCoating.Web/Views/Help/Index.cshtml | 2 +-
.../Views/Help/Inventory.cshtml | 98 ++++-----
.../Views/Help/Invoices.cshtml | 48 ++---
src/PowderCoating.Web/Views/Help/Jobs.cshtml | 96 ++++-----
.../Views/Help/PurchaseOrders.cshtml | 10 +-
.../Views/Help/Quotes.cshtml | 80 ++++----
.../Views/Help/Reports.cshtml | 96 ++++-----
.../Views/Help/Settings.cshtml | 190 +++++++++---------
.../Views/Help/UserProfile.cshtml | 40 ++--
.../Views/Help/Vendors.cshtml | 36 ++--
.../Views/Home/Accessibility.cshtml | 14 +-
src/PowderCoating.Web/Views/Home/Index.cshtml | 4 +-
.../Views/Home/Privacy.cshtml | 18 +-
.../Views/Home/Security.cshtml | 18 +-
.../Views/Home/ServiceLevelAgreement.cshtml | 18 +-
.../Views/Home/TermsOfService.cshtml | 4 +-
.../Views/InAppNotifications/Index.cshtml | 2 +-
.../Views/Inventory/Create.cshtml | 30 +--
.../Views/Inventory/Details.cshtml | 20 +-
.../Views/Inventory/Edit.cshtml | 24 +--
.../Views/Inventory/Index.cshtml | 8 +-
.../Views/Inventory/Label.cshtml | 6 +-
.../Views/Inventory/Ledger.cshtml | 22 +-
.../Views/Inventory/SamplePanels.cshtml | 22 +-
.../Views/Inventory/Scan.cshtml | 10 +-
.../_InventoryColorFamilyScripts.cshtml | 12 +-
.../Views/Inventory/_LabelScanModal.cshtml | 4 +-
.../Views/Invoices/Create.cshtml | 16 +-
.../Views/Invoices/Details.cshtml | 42 ++--
.../Views/Invoices/Edit.cshtml | 8 +-
.../Views/Invoices/Index.cshtml | 2 +-
.../Views/Invoices/OnlinePayments.cshtml | 10 +-
.../Views/JobTemplates/Details.cshtml | 6 +-
.../Views/JobTemplates/Edit.cshtml | 4 +-
src/PowderCoating.Web/Views/Jobs/Board.cshtml | 6 +-
.../Views/Jobs/Create.cshtml | 14 +-
.../Views/Jobs/Details.cshtml | 8 +-
src/PowderCoating.Web/Views/Jobs/Edit.cshtml | 12 +-
.../Views/Jobs/EditItems.cshtml | 4 +-
src/PowderCoating.Web/Views/Jobs/Index.cshtml | 2 +-
.../Views/Jobs/Intake.cshtml | 6 +-
.../Views/Jobs/ShopDisplay.cshtml | 12 +-
.../Views/Jobs/ShopMobile.cshtml | 4 +-
.../Views/Jobs/StatusBump.cshtml | 6 +-
.../Views/Jobs/WorkOrder.cshtml | 12 +-
.../Views/JobsPriority/Index.cshtml | 18 +-
.../Views/JournalEntries/Details.cshtml | 2 +-
.../Views/Kiosk/Activate.cshtml | 2 +-
.../Views/Kiosk/Intake/Confirmation.cshtml | 2 +-
.../Views/Kiosk/Intake/Job.cshtml | 2 +-
.../Views/Kiosk/Intake/Terms.cshtml | 4 +-
.../Views/Kiosk/Welcome.cshtml | 2 +-
.../Views/Maintenance/Create.cshtml | 4 +-
.../Views/Maintenance/Delete.cshtml | 4 +-
.../Views/Maintenance/Details.cshtml | 2 +-
.../Views/Maintenance/Edit.cshtml | 4 +-
.../Views/Maintenance/Index.cshtml | 2 +-
.../ManufacturerLookupPatterns/Create.cshtml | 14 +-
.../ManufacturerLookupPatterns/Edit.cshtml | 14 +-
.../ManufacturerLookupPatterns/Index.cshtml | 10 +-
.../Views/NotificationLogs/Index.cshtml | 2 +-
.../Views/OnboardingProgress/Index.cshtml | 10 +-
.../Views/OvenScheduler/Index.cshtml | 40 ++--
.../Views/OvenScheduler/_BatchCard.cshtml | 2 +-
.../Views/Passkey/EnrollPrompt.cshtml | 6 +-
.../Views/Passkey/Manage.cshtml | 2 +-
.../Views/Pay/Deposit.cshtml | 10 +-
src/PowderCoating.Web/Views/Pay/Index.cshtml | 12 +-
.../Views/Payment/Index.cshtml | 6 +-
.../Views/Payment/Success.cshtml | 2 +-
.../PlatformNotifications/Details.cshtml | 2 +-
.../Views/PlatformNotifications/Index.cshtml | 2 +-
.../Views/PlatformSubscription/Edit.cshtml | 4 +-
.../Views/PlatformSubscription/Index.cshtml | 4 +-
.../Views/PlatformUsers/Index.cshtml | 4 +-
.../Views/PowderInsights/Index.cshtml | 20 +-
.../PowderInsights/_JobPowderSummary.cshtml | 8 +-
.../Views/PricingTiers/Create.cshtml | 6 +-
.../Views/PricingTiers/Edit.cshtml | 8 +-
.../Views/PricingTiers/Index.cshtml | 6 +-
.../Views/Profile/Index.cshtml | 58 +++---
.../Views/PurchaseOrders/Create.cshtml | 14 +-
.../Views/PurchaseOrders/Details.cshtml | 4 +-
.../Views/PurchaseOrders/Edit.cshtml | 8 +-
.../Views/PurchaseOrders/Index.cshtml | 6 +-
.../Views/PurchaseOrders/Receive.cshtml | 12 +-
.../SelectLowStockVendor.cshtml | 2 +-
.../Views/QuoteApproval/Confirmation.cshtml | 2 +-
.../Views/Quotes/Create.cshtml | 24 +--
.../Views/Quotes/Delete.cshtml | 4 +-
.../Views/Quotes/Details.cshtml | 48 ++---
.../Views/Quotes/Edit.cshtml | 26 +--
.../Views/Quotes/Index.cshtml | 2 +-
.../Views/RecurringTemplates/Create.cshtml | 14 +-
.../Views/RecurringTemplates/Edit.cshtml | 14 +-
.../Views/RecurringTemplates/Index.cshtml | 2 +-
.../Views/Registration/Welcome.cshtml | 28 +--
.../Views/ReleaseNotes/Manage.cshtml | 4 +-
.../Views/Reports/AnomalyDetection.cshtml | 6 +-
.../Views/Reports/ApAging.cshtml | 24 +--
.../Views/Reports/ArAging.cshtml | 26 +--
.../Views/Reports/BalanceSheet.cshtml | 2 +-
.../Views/Reports/BudgetVsActual.cshtml | 2 +-
.../Views/Reports/CashFlowForecast.cshtml | 10 +-
.../Views/Reports/CashFlowStatement.cshtml | 2 +-
.../Views/Reports/CustomerRetention.cshtml | 12 +-
.../Views/Reports/FinancialQuery.cshtml | 4 +-
.../Views/Reports/Index.cshtml | 48 ++---
.../Views/Reports/InventoryTurnover.cshtml | 2 +-
.../Views/Reports/InvoiceAgingDetail.cshtml | 14 +-
.../Views/Reports/JobStatusAging.cshtml | 2 +-
.../Views/Reports/Landing.cshtml | 16 +-
.../Views/Reports/OperationsReport.cshtml | 2 +-
.../Views/Reports/PowderConsumption.cshtml | 4 +-
.../Views/Reports/PowderUsage.cshtml | 4 +-
.../Views/Reports/ProfitAndLoss.cshtml | 20 +-
.../Views/Reports/SalesAndIncome.cshtml | 10 +-
.../Views/Reports/SalesByCustomer.cshtml | 2 +-
.../Views/Reports/SalesTax.cshtml | 12 +-
.../Views/Reports/TaxReporting1099.cshtml | 8 +-
.../Views/Revenue/Index.cshtml | 8 +-
.../Views/SeedData/Index.cshtml | 4 +-
.../Views/SetupWizard/Complete.cshtml | 4 +-
.../Views/SetupWizard/Step1.cshtml | 22 +-
.../Views/SetupWizard/Step10.cshtml | 4 +-
.../Views/SetupWizard/Step11.cshtml | 2 +-
.../Views/SetupWizard/Step12.cshtml | 4 +-
.../Views/SetupWizard/Step13.cshtml | 10 +-
.../Views/SetupWizard/Step14.cshtml | 2 +-
.../Views/SetupWizard/Step15.cshtml | 2 +-
.../Views/SetupWizard/Step16.cshtml | 2 +-
.../Views/SetupWizard/Step17.cshtml | 2 +-
.../Views/SetupWizard/Step18.cshtml | 2 +-
.../Views/SetupWizard/Step2.cshtml | 4 +-
.../Views/SetupWizard/Step3.cshtml | 46 ++---
.../Views/SetupWizard/Step4.cshtml | 8 +-
.../Views/SetupWizard/Step5.cshtml | 4 +-
.../Views/SetupWizard/Step6.cshtml | 6 +-
.../Views/SetupWizard/Step7.cshtml | 2 +-
.../Views/SetupWizard/Step8.cshtml | 2 +-
.../Views/SetupWizard/Step9.cshtml | 2 +-
.../_QbMigrationWizardModal.cshtml | 6 +-
.../Views/Shared/_AiHelpWidget.cshtml | 4 +-
.../Views/Shared/_AiQuickQuoteWidget.cshtml | 6 +-
.../Views/Shared/_KioskLayout.cshtml | 8 +-
.../Views/Shared/_Layout.cshtml | 34 ++--
.../Views/Shared/_SectionHeader.cshtml | 2 +-
.../Views/SmsAgreements/Index.cshtml | 20 +-
.../Views/SmsConsentAudit/Index.cshtml | 12 +-
.../Views/StorageMigration/Index.cshtml | 8 +-
.../Views/StripeEvents/Details.cshtml | 6 +-
.../Views/StripeEvents/Index.cshtml | 10 +-
.../Views/SubscriptionManagement/Index.cshtml | 16 +-
.../SubscriptionManagement/Manage.cshtml | 22 +-
.../Views/SystemInfo/Index.cshtml | 4 +-
.../Views/SystemLogs/Index.cshtml | 24 +--
.../Views/TaxRates/Index.cshtml | 4 +-
.../Views/Tools/Index.cshtml | 2 +-
.../Views/TwoFactorSetup/Setup.cshtml | 4 +-
.../Views/Unsubscribe/EmailConfirm.cshtml | 2 +-
.../Views/UsageQuota/Index.cshtml | 8 +-
.../Views/UserActivity/Index.cshtml | 2 +-
.../Views/UserActivity/Online.cshtml | 8 +-
.../Views/VendorCredits/Create.cshtml | 6 +-
.../Views/VendorCredits/Details.cshtml | 6 +-
.../Views/VendorCredits/Index.cshtml | 2 +-
.../Views/Vendors/Create.cshtml | 4 +-
.../Views/Vendors/Details.cshtml | 4 +-
.../Views/Vendors/Edit.cshtml | 4 +-
.../Views/Vendors/Index.cshtml | 10 +-
.../Views/Vendors/Statement.cshtml | 4 +-
tools/Fix-Encoding.ps1 | 125 ++++++++++++
252 files changed, 1785 insertions(+), 1633 deletions(-)
create mode 100644 .githooks/pre-commit
create mode 100644 tools/Fix-Encoding.ps1
diff --git a/.githooks/pre-commit b/.githooks/pre-commit
new file mode 100644
index 0000000..bd722a6
--- /dev/null
+++ b/.githooks/pre-commit
@@ -0,0 +1,27 @@
+#!/bin/sh
+# Pre-commit hook: block commits containing corrupted Unicode in .cshtml files.
+#
+# All corruption variants start with the UTF-8 byte sequence for a-circumflex
+# followed by euro-sign (bytes C3 A2 E2 82 AC), which is the first two chars
+# of every known corruption pattern. Grep for that byte sequence in staged files.
+
+STAGED=$(git diff --cached --name-only | grep '\.cshtml$')
+if [ -z "$STAGED" ]; then
+ exit 0
+fi
+
+# $'\xc3\xa2\xe2\x82\xac' = UTF-8 bytes for a-circumflex + euro-sign
+CORRUPT=$(echo "$STAGED" | xargs grep -l $'\xc3\xa2\xe2\x82\xac' 2>/dev/null)
+
+if [ -n "$CORRUPT" ]; then
+ echo ""
+ echo "ERROR: Corrupted Unicode characters detected in staged .cshtml files:"
+ echo "$CORRUPT" | sed 's/^/ /'
+ echo ""
+ echo "Fix by running: .\\tools\\Fix-Encoding.ps1"
+ echo "Then re-stage the files and commit again."
+ echo ""
+ exit 1
+fi
+
+exit 0
diff --git a/src/PowderCoating.Web/Views/AccountDataExport/Index.cshtml b/src/PowderCoating.Web/Views/AccountDataExport/Index.cshtml
index cfe22eb..1640c28 100644
--- a/src/PowderCoating.Web/Views/AccountDataExport/Index.cshtml
+++ b/src/PowderCoating.Web/Views/AccountDataExport/Index.cshtml
@@ -72,14 +72,14 @@
- Excel (.xlsx) — all sheets in one file
+ Excel (.xlsx) — all sheets in one file
- CSV (.zip) — one file per sheet
+ CSV (.zip) — one file per sheet
@@ -111,7 +111,7 @@
document.getElementById('exportForm').addEventListener('submit', function () {
var btn = document.getElementById('exportBtn');
btn.disabled = true;
- btn.innerHTML = ' Generating…';
+ btn.innerHTML = ' Generating…';
// Re-enable after 10s in case browser blocks the download dialog
setTimeout(function () {
btn.disabled = false;
diff --git a/src/PowderCoating.Web/Views/AccountingExport/Index.cshtml b/src/PowderCoating.Web/Views/AccountingExport/Index.cshtml
index 6fa0b06..2472c1c 100644
--- a/src/PowderCoating.Web/Views/AccountingExport/Index.cshtml
+++ b/src/PowderCoating.Web/Views/AccountingExport/Index.cshtml
@@ -49,7 +49,7 @@
QuickBooks Desktop
-
IIF files — import directly via File > Utilities > Import
+
IIF files — import directly via File > Utilities > Import
customers.iif
invoices_payments.iif
@@ -146,7 +146,7 @@
});
}
- // Init — mark CSV as selected by default
+ // Init — mark CSV as selected by default
document.getElementById('fmt-csv').checked = true;
updateFormatCards();
@@ -154,7 +154,7 @@
document.getElementById('exportForm').addEventListener('submit', function() {
const btn = document.getElementById('exportBtn');
btn.disabled = true;
- btn.innerHTML = '
Generating…';
+ btn.innerHTML = '
Generating…';
setTimeout(function() { btn.disabled = false; btn.innerHTML = '
Download Export Package'; }, 8000);
});
diff --git a/src/PowderCoating.Web/Views/Accounts/Create.cshtml b/src/PowderCoating.Web/Views/Accounts/Create.cshtml
index b819cf2..f59330b 100644
--- a/src/PowderCoating.Web/Views/Accounts/Create.cshtml
+++ b/src/PowderCoating.Web/Views/Accounts/Create.cshtml
@@ -1,11 +1,11 @@
-@model PowderCoating.Application.DTOs.Accounting.CreateAccountDto
+@model PowderCoating.Application.DTOs.Accounting.CreateAccountDto
@using PowderCoating.Core.Enums
@{
ViewData["Title"] = "New Account";
ViewData["PageIcon"] = "bi-journal-plus";
ViewData["PageHelpTitle"] = "New Account";
- ViewData["PageHelpContent"] = "Add a custom account to the Chart of Accounts. Select a Sub-Type first — it auto-sets the Account Type. Use conventional numbering: 1000s = Assets, 2000s = Liabilities, 3000s = Equity, 4000s = Revenue, 5000s = Cost of Goods, 6000s+ = Expenses.";
+ ViewData["PageHelpContent"] = "Add a custom account to the Chart of Accounts. Select a Sub-Type first — it auto-sets the Account Type. Use conventional numbering: 1000s = Assets, 2000s = Liabilities, 3000s = Equity, 4000s = Revenue, 5000s = Cost of Goods, 6000s+ = Expenses.";
bool isInline = ViewBag.Inline == true;
}
@@ -31,7 +31,7 @@
+ data-bs-content="A numeric code for sorting and organizing accounts. Convention: 1000–1999 Assets, 2000–2999 Liabilities, 3000–3999 Equity, 4000–4999 Revenue, 5000–5999 Cost of Goods, 6000–9999 Expenses. Must be unique. Sub-accounts can use decimals (e.g. 6100.1).">
@@ -55,7 +55,7 @@
- — Select Type —
+ — Select Type —
@@ -70,7 +70,7 @@
- — Select Sub-Type —
+ — Select Sub-Type —
- — None (top-level account) —
+ — None (top-level account) —
@@ -152,7 +152,7 @@
diff --git a/src/PowderCoating.Web/Views/CompanySettings/EditTemplate.cshtml b/src/PowderCoating.Web/Views/CompanySettings/EditTemplate.cshtml
index 0b877da..cd7a059 100644
--- a/src/PowderCoating.Web/Views/CompanySettings/EditTemplate.cshtml
+++ b/src/PowderCoating.Web/Views/CompanySettings/EditTemplate.cshtml
@@ -1,6 +1,6 @@
@model PowderCoating.Application.DTOs.Notification.NotificationTemplateDto
@{
- ViewData["Title"] = $"Edit Template — {Model.DisplayName}";
+ ViewData["Title"] = $"Edit Template — {Model.DisplayName}";
ViewData["PageIcon"] = "bi-envelope-gear";
var placeholders = ViewBag.Placeholders as List<(string Placeholder, string Description)>
?? new List<(string, string)>();
@@ -70,7 +70,7 @@
@if (isEmail)
{
-
+
@@ -131,7 +131,7 @@
+ title="@description — click to copy">
@placeholder
Copied!
diff --git a/src/PowderCoating.Web/Views/CompanySettings/Index.cshtml b/src/PowderCoating.Web/Views/CompanySettings/Index.cshtml
index 9480002..310aec8 100644
--- a/src/PowderCoating.Web/Views/CompanySettings/Index.cshtml
+++ b/src/PowderCoating.Web/Views/CompanySettings/Index.cshtml
@@ -1,4 +1,4 @@
-@model PowderCoating.Application.DTOs.Company.CompanySettingsDto
+@model PowderCoating.Application.DTOs.Company.CompanySettingsDto
@{
ViewData["Title"] = "Company Settings";
ViewData["PageIcon"] = "bi-building";
@@ -118,7 +118,7 @@
+ data-bs-content="This information appears on every customer-facing document — quotes, invoices, and PDFs. Keep the company name, address, and email accurate so customers see the right details. The <strong>Primary Contact Email</strong> is used as the reply-to address on all outgoing notifications.<br><br><a href='/Help/Settings#company-information' target='_blank'>Learn more →</a>">
@@ -165,39 +165,39 @@
Time Zone
- Eastern (ET) — New York
- Central (CT) — Chicago
- Mountain (MT) — Denver
- Mountain no-DST — Phoenix
- Pacific (PT) — Los Angeles
- Alaska (AKT) — Anchorage
- Hawaii (HT) — Honolulu
+ Eastern (ET) — New York
+ Central (CT) — Chicago
+ Mountain (MT) — Denver
+ Mountain no-DST — Phoenix
+ Pacific (PT) — Los Angeles
+ Alaska (AKT) — Anchorage
+ Hawaii (HT) — Honolulu
- Atlantic (AT) — Halifax
- Eastern — Toronto
- Central — Winnipeg
- Mountain — Edmonton
- Pacific — Vancouver
+ Atlantic (AT) — Halifax
+ Eastern — Toronto
+ Central — Winnipeg
+ Mountain — Edmonton
+ Pacific — Vancouver
- GMT/BST — London
- CET — Paris / Berlin
- EET — Helsinki
- MSK — Moscow
+ GMT/BST — London
+ CET — Paris / Berlin
+ EET — Helsinki
+ MSK — Moscow
- GST — Dubai
- IST — India
- ICT — Bangkok
- CST — Beijing / Shanghai
- JST — Tokyo
- AEST — Sydney
- NZST — Auckland
+ GST — Dubai
+ IST — India
+ ICT — Bangkok
+ CST — Beijing / Shanghai
+ JST — Tokyo
+ AEST — Sydney
+ NZST — Auckland
- BRT — São Paulo
- ART — Buenos Aires
+ BRT — São Paulo
+ ART — Buenos Aires
UTC
@@ -243,7 +243,7 @@
}
else
{
- No period lock set — all dates are open.
+ No period lock set — all dates are open.
}
@@ -506,7 +506,7 @@
+ data-bs-content="The hourly cost of running each piece of equipment, including energy and depreciation. These are added to quote items based on the prep services selected. The <strong>Default Oven Rate</strong> is used on quotes where no named oven is chosen — add individual shop ovens below if you have multiple ovens with different capacities and costs.">
@@ -554,7 +554,7 @@
+ data-bs-content="<strong>Markup mode</strong> adds a % on top of material costs only (labor and equipment pass through at cost). <strong>Margin mode</strong> targets a gross margin % of the total selling price — e.g. 30% margin on a $100 cost base gives a $142.86 price. Note: margin % and markup % are not the same number. <strong>Shop Minimum</strong> sets a floor price for any job.">
@@ -678,7 +678,7 @@
+ data-bs-content="A percentage added to the price of <strong>calculated items</strong> based on how intricate the part is. When adding an item in a quote, staff select a complexity level — the system then applies this multiplier to account for the extra time and care needed. <em>Simple</em> = 0% (flat panels, basic shapes). <em>Extreme</em> = highly detailed, tight recesses, masking-intensive parts.">
@@ -775,11 +775,11 @@
=
- —
+ —
Use
- W × D × H of oven interior — 20% deducted for rack & wall depth
+ W × D × H of oven interior — 20% deducted for rack & wall depth
Display Order
@@ -810,7 +810,7 @@
+ data-bs-content="Describe your shop's specialties, typical items, and pricing style in plain language. This text is injected into the AI's system prompt every time a photo quote is analysed — the more specific you are, the better calibrated the estimates will be for your business. You can also describe anything the AI tends to get wrong. <br><br><strong>Additionally</strong>, the AI automatically learns from quotes your team accepted without overriding — those become calibration examples that improve accuracy over time.">
@@ -821,9 +821,9 @@
Shop Description
+ placeholder="Examples:
• We specialise in automotive restoration — wheels, frames, suspension brackets, and roll cages are our bread and butter.
• Our customers expect premium pricing. We rarely work on items over 20 sqft.
• Most items come to us already stripped; sandblasting adds roughly 15 min per item on average.
• We use a 2-stage cure cycle — pre-heat 10 min, coat, cure 20 min at 400°F.">@(Model.OperatingCosts?.AiContextProfile)
- Plain language — write it as if briefing a new estimator on your shop.
+ Plain language — write it as if briefing a new estimator on your shop.
@(Model.OperatingCosts?.AiContextProfile?.Length ?? 0) /2000
@@ -832,7 +832,7 @@
Save AI Profile
+ title="Build a suggested profile from your existing settings — ovens, workers, inventory categories, and rates">
Generate from my settings
@@ -843,9 +843,9 @@
How AI Learning Works
-
Layer 1 — Pricing config: Your operating costs (labor, equipment, markup) are always injected automatically.
-
Layer 2 — Your shop profile: The description you write here is added to every AI analysis, guiding estimates toward your typical work.
-
Layer 3 — Automatic learning: Each time your team accepts an AI estimate without changing it, that item is silently added as a calibration example. The AI improves on its own the more you use it.
+
Layer 1 — Pricing config: Your operating costs (labor, equipment, markup) are always injected automatically.
+
Layer 2 — Your shop profile: The description you write here is added to every AI analysis, guiding estimates toward your typical work.
+
Layer 3 — Automatic learning: Each time your team accepts an AI estimate without changing it, that item is silently added as a calibration example. The AI improves on its own the more you use it.
@@ -874,10 +874,10 @@
Shop Size
- Garage — Home setup, part-time
- Small — 1–5 person shop
- Medium — Established shop, 5–10 people
- Large — High-volume, 10+ people
+ Garage — Home setup, part-time
+ Small — 1–5 person shop
+ Medium — Established shop, 5–10 people
+ Large — High-volume, 10+ people
Used by the AI when estimating job complexity and throughput.
@@ -978,11 +978,11 @@
Default Currency
- USD — US Dollar
- CAD — Canadian Dollar
- EUR — Euro
- GBP — British Pound
- AUD — Australian Dollar
+ USD — US Dollar
+ CAD — Canadian Dollar
+ EUR — Euro
+ GBP — British Pound
+ AUD — Australian Dollar
@@ -1046,7 +1046,7 @@
+ data-bs-content="The prefix is combined with a date stamp and sequence number to form record IDs — for example prefix <strong>QT</strong> produces <em>QT-2603-0042</em>. Change the prefix to match your preferred numbering convention. Changing it only affects <strong>new</strong> records; existing numbers are not renamed.">
@@ -1082,7 +1082,7 @@
+ data-bs-content="Controls how jobs are created and flow through your shop. <strong>Require Customer PO</strong> enforces that a PO number is entered before a job can be saved — useful for commercial accounts. <strong>Allow Customer Approval</strong> enables the approval step in the job workflow — when a quote is approved, the job moves to an Approved status before work begins.">
@@ -1141,7 +1141,7 @@
+ data-bs-content="Controls which events send emails to your team and customers. Set the <strong>From Email Address</strong> to a domain you control — using a domain-verified address prevents emails landing in spam. If left blank, the system default address is used. Turn off notification types you don't need to avoid inbox noise.">
@@ -1311,7 +1311,7 @@
+ data-bs-content="Customise the subject and body of every automated email sent by the system — job status updates, quote approvals, invoice reminders, and more. Templates use <strong>{{placeholder}}</strong> tokens that are replaced with live data when the email is sent. Click <strong>Edit</strong> on any row to modify it; use <strong>Reset to Default</strong> to restore the original wording at any time.<br><br>Changes take effect immediately — the next triggered notification will use the updated template.">
@@ -1371,7 +1371,7 @@
+ data-bs-content="Controls how long records are kept. Most businesses set quote and job retention to <strong>7 years</strong> to satisfy tax and audit requirements. <strong>Deleted record retention</strong> is the grace period after a soft-delete before the record is permanently purged — useful if someone accidentally deletes something.">
@@ -1433,7 +1433,7 @@
+ data-bs-content="Lookups are the dropdown options that appear throughout the app — job statuses, priorities, quote statuses, and more. You can rename labels, change colours, and reorder them to match your shop's terminology. <strong>Status codes</strong> drive workflow logic and should not be changed unless you understand the impact.">
@@ -1869,7 +1869,7 @@
- Printed in italic at the bottom of every blank work order. Supports plain text — use * or ** for visual emphasis.
+ Printed in italic at the bottom of every blank work order. Supports plain text — use * or ** for visual emphasis.
@@ -2168,7 +2168,7 @@
@@ -2178,9 +2178,9 @@
1. Prior Express Written Consent Required
-
You must obtain clear, documented consent from each customer before sending them any SMS message. This means each customer must have explicitly agreed — in writing or through a recorded digital interaction — that they wish to receive text messages from your business. Enabling this feature is not consent on their behalf. You must collect and record their authorization individually, before enabling SMS for their account in this system.
+
You must obtain clear, documented consent from each customer before sending them any SMS message. This means each customer must have explicitly agreed — in writing or through a recorded digital interaction — that they wish to receive text messages from your business. Enabling this feature is not consent on their behalf. You must collect and record their authorization individually, before enabling SMS for their account in this system.
-
2. Federal Law Governs SMS — Fines Are Real
+
2. Federal Law Governs SMS — Fines Are Real
The Telephone Consumer Protection Act (TCPA) , enforced by the Federal Communications Commission (FCC), imposes fines of $500 to $1,500 per individual message sent without proper authorization. These fines apply per text, not per customer. A single campaign to 100 unconsented recipients could result in exposure of $50,000 to $150,000. The FCC and private plaintiffs both actively pursue TCPA violations.
3. Opt-Out Requests Must Be Honored Immediately
@@ -2189,7 +2189,7 @@
4. Message Rates & Content Restrictions
Every message sent must include your business name and an opt-out reminder (e.g., "Reply STOP to opt out"). Messages must be directly relevant to the service the customer consented to receive and must not contain solicitations, promotions, or third-party offers unless the customer has separately consented to those.
-
5. Your Responsibility — Not Ours
+
5. Your Responsibility — Not Ours
Powder Coating Logix provides this feature as a communication tool only. We are not responsible for how you use it. You agree that your company is solely responsible for obtaining proper consent, maintaining records of that consent, honoring opt-outs, and ensuring all outbound messages comply with the TCPA, FCC regulations, and any applicable state laws. You agree to indemnify and hold Powder Coating Logix harmless from any claims, fines, or damages arising from your company's use of SMS.
@@ -2201,7 +2201,7 @@
— navigate relative to the button
+ // Bootstrap teleports modals to — navigate relative to the button
const modalEl = btn ? btn.closest('.modal') : document.getElementById('gcModal');
const q = sel => modalEl ? modalEl.querySelector(sel) : document.querySelector(sel);
diff --git a/src/PowderCoating.Web/Views/Invoices/Details.cshtml b/src/PowderCoating.Web/Views/Invoices/Details.cshtml
index 30bd6e2..1b13309 100644
--- a/src/PowderCoating.Web/Views/Invoices/Details.cshtml
+++ b/src/PowderCoating.Web/Views/Invoices/Details.cshtml
@@ -17,8 +17,8 @@
var emailOptedOut = hasEmail && !Model.CustomerNotifyByEmail;
var smsPhone = !string.IsNullOrWhiteSpace(Model.CustomerMobilePhone) ? Model.CustomerMobilePhone : Model.CustomerPhone;
var hasSms = !string.IsNullOrWhiteSpace(smsPhone) && Model.CustomerNotifyBySms;
- var showSendModal = hasEmail && !emailOptedOut && hasSms; // both channels — show choice modal
- var directSendSms = !hasEmail && hasSms; // SMS only — skip modal
+ var showSendModal = hasEmail && !emailOptedOut && hasSms; // both channels — show choice modal
+ var directSendSms = !hasEmail && hasSms; // SMS only — skip modal
var hasAvailableCredits = ViewBag.AvailableCreditMemos != null && ((IEnumerable)ViewBag.AvailableCreditMemos).Any();
var canIssueRefund = !isDraft && !isVoided && Model.AmountPaid > 0;
var canApplyCredit = !isVoided && Model.BalanceDue > 0 && hasAvailableCredits;
@@ -80,7 +80,7 @@
- @Model.CustomerName has no email address on file — you'll be prompted to enter one when sending.
+ @Model.CustomerName has no email address on file — you'll be prompted to enter one when sending.
Add one in customer settings .
@@ -179,12 +179,12 @@
Due Date
- @(Model.DueDate.HasValue ? Model.DueDate.Value.ToString("MMMM d, yyyy") : "—")
+ @(Model.DueDate.HasValue ? Model.DueDate.Value.ToString("MMMM d, yyyy") : "—")
Sent Date
-
@(Model.SentDate.HasValue ? Model.SentDate.Value.ToString("MMMM d, yyyy") : "—")
+
@(Model.SentDate.HasValue ? Model.SentDate.Value.ToString("MMMM d, yyyy") : "—")
@if (!string.IsNullOrWhiteSpace(Model.CustomerPO))
{
@@ -350,7 +350,7 @@
- @(gcItem.Description.Contains("for ") ? gcItem.Description.Substring(gcItem.Description.IndexOf("for ") + 4).TrimEnd(')') : "—")
+ @(gcItem.Description.Contains("for ") ? gcItem.Description.Substring(gcItem.Description.IndexOf("for ") + 4).TrimEnd(')') : "—")
@gcItem.TotalPrice.ToString("C")
@@ -396,7 +396,7 @@
@p.PaymentDate.ToString("MM/dd/yyyy")
@p.PaymentMethodDisplay
- @(p.Reference ?? "—")
+ @(p.Reference ?? "—")
@if (!string.IsNullOrEmpty(p.DepositAccountName))
{
@@ -404,10 +404,10 @@
}
else
{
- —
+ —
}
- @(p.RecordedByName ?? "—")
+ @(p.RecordedByName ?? "—")
@p.Amount.ToString("C")
@if (!isVoided)
@@ -463,7 +463,7 @@
@r.RefundDate.ToString("MM/dd/yyyy")
@r.RefundMethodDisplay
@r.Reason
- @(r.Reference ?? "—")
+ @(r.Reference ?? "—")
@r.Status
(@r.Amount.ToString("C"))
@@ -575,7 +575,7 @@
+ data-bs-content="Workflow: Edit (Draft only) → Send Invoice (locks it, emails customer) → Record Payment. Partial payments are supported — record multiple payments until fully paid. Void cancels the invoice and reverses the customer balance without deleting history. Delete is only available for Drafts.">
@@ -601,7 +601,7 @@
}
else if (showSendModal)
{
- @* Both email + SMS available — let staff choose *@
+ @* Both email + SMS available — let staff choose *@
Send Invoice
@@ -609,7 +609,7 @@
}
else if (directSendSms)
{
- @* SMS only — send directly *@
+ @* SMS only — send directly *@
Send Invoice via SMS
@@ -792,7 +792,7 @@
{
- The refund amount will be added to the customer's store credit balance immediately — no manual action needed.
+ The refund amount will be added to the customer's store credit balance immediately — no manual action needed.
Amount *
@@ -1326,7 +1326,7 @@
Select Credit Memo *
- — Select —
+ — Select —
@foreach (var item in (IEnumerable)ViewBag.AvailableCreditMemos)
{
@item.Text
@@ -1340,7 +1340,7 @@
-
Balance due: @Model.BalanceDue.ToString("C") — the system will cap at the memo's remaining balance.
+
Balance due: @Model.BalanceDue.ToString("C") — the system will cap at the memo's remaining balance.
${errorRow}`;
}).join('');
diff --git a/src/PowderCoating.Web/Views/Invoices/Edit.cshtml b/src/PowderCoating.Web/Views/Invoices/Edit.cshtml
index 2b21404..7b4021a 100644
--- a/src/PowderCoating.Web/Views/Invoices/Edit.cshtml
+++ b/src/PowderCoating.Web/Views/Invoices/Edit.cshtml
@@ -1,4 +1,4 @@
-@model PowderCoating.Application.DTOs.Invoice.UpdateInvoiceDto
+@model PowderCoating.Application.DTOs.Invoice.UpdateInvoiceDto
@{
ViewData["Title"] = "Edit Invoice";
@@ -38,7 +38,7 @@
+ data-bs-content="Invoice Date is the date of issue and the reference for payment terms. Due Date drives overdue status and A/R aging. Payment Terms prints on the invoice — changing it here only affects this invoice. Draft, Sent, and Overdue invoices can be edited; Paid and Partially Paid invoices are locked.">
@@ -163,7 +163,7 @@
+ data-bs-content="Customer Notes appear on the printed and emailed invoice — use these for payment instructions, thank-you messages, or job-specific reminders. Internal Notes are only visible to staff in the app and are never sent to the customer.">
@@ -196,7 +196,7 @@
+ data-bs-content="Subtotal = sum of all line item totals. Discount is a flat dollar amount deducted before tax. Tax % is applied to (Subtotal âˆ' Discount). Both default from the company settings but can be overridden for this invoice.">
diff --git a/src/PowderCoating.Web/Views/Invoices/Index.cshtml b/src/PowderCoating.Web/Views/Invoices/Index.cshtml
index 11945b9..99d0c72 100644
--- a/src/PowderCoating.Web/Views/Invoices/Index.cshtml
+++ b/src/PowderCoating.Web/Views/Invoices/Index.cshtml
@@ -85,7 +85,7 @@
@if (thisMonthOnly && statusFilter == InvoiceStatus.Paid)
{
- Paid — @DateTime.Now.ToString("MMMM yyyy")
+ Paid — @DateTime.Now.ToString("MMMM yyyy")
}
else if (thisMonthOnly)
diff --git a/src/PowderCoating.Web/Views/Invoices/OnlinePayments.cshtml b/src/PowderCoating.Web/Views/Invoices/OnlinePayments.cshtml
index e06d564..79882df 100644
--- a/src/PowderCoating.Web/Views/Invoices/OnlinePayments.cshtml
+++ b/src/PowderCoating.Web/Views/Invoices/OnlinePayments.cshtml
@@ -107,7 +107,7 @@
{
var net = inv.OnlineAmountPaid;
var gross = inv.OnlineAmountPaid + inv.OnlineSurchargeCollected;
- var dateDisplay = inv.PaidDate.HasValue ? inv.PaidDate.Value.ToString("MMM d, yyyy") : (inv.UpdatedAt?.ToString("MMM d, yyyy") ?? "—");
+ var dateDisplay = inv.PaidDate.HasValue ? inv.PaidDate.Value.ToString("MMM d, yyyy") : (inv.UpdatedAt?.ToString("MMM d, yyyy") ?? "—");
var statusClass = inv.OnlinePaymentStatus switch
{
PowderCoating.Core.Enums.OnlinePaymentStatus.Paid => "bg-success-subtle text-success",
@@ -117,7 +117,7 @@
};
var customerName = inv.Customer != null
? (inv.Customer.CompanyName ?? $"{inv.Customer.ContactFirstName} {inv.Customer.ContactLastName}".Trim())
- : "—";
+ : "—";
@@ -134,7 +134,7 @@
@if (!string.IsNullOrEmpty(inv.StripePaymentIntentId))
{
- @inv.StripePaymentIntentId[..Math.Min(20, inv.StripePaymentIntentId.Length)]…
+ @inv.StripePaymentIntentId[..Math.Min(20, inv.StripePaymentIntentId.Length)]…
}
@@ -185,12 +185,12 @@
@foreach (var r in Model.Refunds)
{
- var invNum = r.Invoice?.InvoiceNumber ?? "—";
+ var invNum = r.Invoice?.InvoiceNumber ?? "—";
var invId = r.Invoice?.Id;
var cust = r.Invoice?.Customer;
var custName = cust != null
? (cust.CompanyName ?? $"{cust.ContactFirstName} {cust.ContactLastName}".Trim())
- : "—";
+ : "—";
var statusClass = r.Status switch
{
PowderCoating.Core.Enums.RefundStatus.Issued => "bg-success-subtle text-success",
diff --git a/src/PowderCoating.Web/Views/JobTemplates/Details.cshtml b/src/PowderCoating.Web/Views/JobTemplates/Details.cshtml
index 5ce4e65..2f6cee6 100644
--- a/src/PowderCoating.Web/Views/JobTemplates/Details.cshtml
+++ b/src/PowderCoating.Web/Views/JobTemplates/Details.cshtml
@@ -72,7 +72,7 @@
}
else
{
- —
+ —
}
@@ -83,13 +83,13 @@
@coat.CoatName
@if (!string.IsNullOrEmpty(coat.ColorName))
{
- — @coat.ColorName
+ — @coat.ColorName
}
}
@if (!item.Coats.Any(c => !c.IsDeleted))
{
- —
+ —
}
diff --git a/src/PowderCoating.Web/Views/JobTemplates/Edit.cshtml b/src/PowderCoating.Web/Views/JobTemplates/Edit.cshtml
index 35b528e..732c218 100644
--- a/src/PowderCoating.Web/Views/JobTemplates/Edit.cshtml
+++ b/src/PowderCoating.Web/Views/JobTemplates/Edit.cshtml
@@ -1,4 +1,4 @@
-@model PowderCoating.Core.Entities.JobTemplate
+@model PowderCoating.Core.Entities.JobTemplate
@{
ViewData["Title"] = $"Edit Template: {Model.Name}";
ViewData["PageIcon"] = "bi-pencil-square";
@@ -31,7 +31,7 @@
@@ -70,7 +70,7 @@
+ data-bs-content="Controls sort order on the shop floor board and job list. Rush and Urgent jobs are highlighted in red/orange. Normal is the default. Raise priority only when the customer has an actual deadline constraint — overuse of Rush dilutes its meaning for the shop floor team.">
@@ -100,7 +100,7 @@
+ data-bs-content="The customer's deadline — when the work must be ready for pickup or delivery. Overdue jobs (past due date and not yet completed) are highlighted in red on the job list.">
@@ -130,7 +130,7 @@
+ data-bs-content="Free-text notes visible to the shop floor worker on the work order. Use this for masking requirements, handling notes, customer preferences, or anything that doesn't fit in the item-level notes — e.g., 'Keep brackets separated, customer allergic to zinc primer'.">
@@ -201,7 +201,7 @@
+ data-bs-content="Each item represents a physical piece being coated. Use the wizard to pick from the catalog, enter custom dimensions, or upload a photo for AI analysis. Each item gets its own coating specification — color, powder, finish, and cure details. You can add multiple coating passes per item for multi-color or primer+topcoat work.">
diff --git a/src/PowderCoating.Web/Views/Jobs/Details.cshtml b/src/PowderCoating.Web/Views/Jobs/Details.cshtml
index fbd776d..9334148 100644
--- a/src/PowderCoating.Web/Views/Jobs/Details.cshtml
+++ b/src/PowderCoating.Web/Views/Jobs/Details.cshtml
@@ -1,4 +1,4 @@
-@model PowderCoating.Application.DTOs.Job.JobDto
+@model PowderCoating.Application.DTOs.Job.JobDto
@{
ViewData["Title"] = $"Job {Model.JobNumber}";
@@ -2796,7 +2796,7 @@
document.getElementById('costingMargin').textContent = `${d.grossMargin}%`;
document.getElementById('costingQuotedMargin').textContent =
- d.quotedPrice > 0 ? `${d.quotedMargin}% (quoted ${fmt(d.quotedPrice)})` : '—';
+ d.quotedPrice > 0 ? `${d.quotedMargin}% (quoted ${fmt(d.quotedPrice)})` : '—';
// Powder detail lines
const pBody = document.getElementById('powderLines');
@@ -2900,9 +2900,9 @@
}
function updateTotals(total) {
- const fmt = total > 0 ? total.toFixed(2) + ' hrs' : '—';
+ const fmt = total > 0 ? total.toFixed(2) + ' hrs' : '—';
document.getElementById('totalHoursDisplay').textContent = fmt;
- document.getElementById('timeEntriesTotalHours').textContent = total > 0 ? total.toFixed(2) : '—';
+ document.getElementById('timeEntriesTotalHours').textContent = total > 0 ? total.toFixed(2) : '—';
}
// -- Modal helpers -------------------------------------------------
diff --git a/src/PowderCoating.Web/Views/Jobs/Edit.cshtml b/src/PowderCoating.Web/Views/Jobs/Edit.cshtml
index bb81cb2..b192c85 100644
--- a/src/PowderCoating.Web/Views/Jobs/Edit.cshtml
+++ b/src/PowderCoating.Web/Views/Jobs/Edit.cshtml
@@ -1,4 +1,4 @@
-@model PowderCoating.Application.DTOs.Job.UpdateJobDto
+@model PowderCoating.Application.DTOs.Job.UpdateJobDto
@using PowderCoating.Core.Entities
@{
@@ -26,7 +26,7 @@
+ data-bs-content="Core job information. Priority and due date are visible on the shop floor board and job list. Customer PO is the customer's own reference number — it appears on invoices. Special Instructions go directly to the shop floor worker on the work order.">
@@ -45,7 +45,7 @@
+ data-bs-content="Tracks where the job is in the workflow: Pending â†' Approved â†' Sandblasting â†' Cleaning â†' Coating â†' Curing â†' QualityCheck â†' Completed â†' ReadyForPickup â†' Delivered. Status changes trigger customer email notifications (if enabled). Use OnHold to pause work without losing progress.">
@@ -57,7 +57,7 @@
+ data-bs-content="Controls sort order on the shop floor board and job list. Rush and Urgent jobs are highlighted in red/orange. Normal is the default. Raise priority only when the customer has an actual deadline constraint — overuse of Rush dilutes its meaning for the shop floor team.">
@@ -85,7 +85,7 @@
+ data-bs-content="The customer's deadline — when the work must be ready for pickup or delivery. Overdue jobs (past due date and not yet completed) are highlighted in red on the job list.">
@@ -170,7 +170,7 @@
+ data-bs-content="Each item represents a physical piece being coated. Use the wizard to pick from the catalog, enter custom dimensions, or upload a photo for AI analysis. Each item gets its own coating specification — color, powder, finish, and cure details. You can add multiple coating passes per item for multi-color or primer+topcoat work.">
diff --git a/src/PowderCoating.Web/Views/Jobs/EditItems.cshtml b/src/PowderCoating.Web/Views/Jobs/EditItems.cshtml
index 62d0758..7bc84af 100644
--- a/src/PowderCoating.Web/Views/Jobs/EditItems.cshtml
+++ b/src/PowderCoating.Web/Views/Jobs/EditItems.cshtml
@@ -1,8 +1,8 @@
-@model PowderCoating.Application.DTOs.Job.JobEditItemsViewModel
+@model PowderCoating.Application.DTOs.Job.JobEditItemsViewModel
@using PowderCoating.Core.Entities
@{
- ViewData["Title"] = $"Edit Items — {Model.JobNumber}";
+ ViewData["Title"] = $"Edit Items — {Model.JobNumber}";
ViewData["PageIcon"] = "bi-list-check";
}
diff --git a/src/PowderCoating.Web/Views/Jobs/Index.cshtml b/src/PowderCoating.Web/Views/Jobs/Index.cshtml
index 6be0d17..5553f09 100644
--- a/src/PowderCoating.Web/Views/Jobs/Index.cshtml
+++ b/src/PowderCoating.Web/Views/Jobs/Index.cshtml
@@ -81,7 +81,7 @@
diff --git a/src/PowderCoating.Web/Views/Jobs/Intake.cshtml b/src/PowderCoating.Web/Views/Jobs/Intake.cshtml
index 67a7c0e..2fd937c 100644
--- a/src/PowderCoating.Web/Views/Jobs/Intake.cshtml
+++ b/src/PowderCoating.Web/Views/Jobs/Intake.cshtml
@@ -1,7 +1,7 @@
@model (PowderCoating.Application.DTOs.Job.JobDto Job, PowderCoating.Application.DTOs.Job.IntakeJobDto Form)
@{
- ViewData["Title"] = $"Part Intake — {Model.Job.JobNumber}";
+ ViewData["Title"] = $"Part Intake — {Model.Job.JobNumber}";
ViewData["PageIcon"] = "bi-box-seam";
var job = Model.Job;
var form = Model.Form;
@@ -122,7 +122,7 @@
{
- Count doesn't match expected — note the discrepancy in condition notes.
+ Count doesn't match expected — note the discrepancy in condition notes.
}
@@ -246,7 +246,7 @@
const formData = new FormData();
formData.append('jobId', jobId);
formData.append('photo', file);
- formData.append('caption', 'Intake — before');
+ formData.append('caption', 'Intake — before');
formData.append('photoType', '0'); // JobPhotoType.Before = 0
const token = document.querySelector('input[name="__RequestVerificationToken"]').value;
diff --git a/src/PowderCoating.Web/Views/Jobs/ShopDisplay.cshtml b/src/PowderCoating.Web/Views/Jobs/ShopDisplay.cshtml
index 6f508aa..50e46e9 100644
--- a/src/PowderCoating.Web/Views/Jobs/ShopDisplay.cshtml
+++ b/src/PowderCoating.Web/Views/Jobs/ShopDisplay.cshtml
@@ -141,7 +141,7 @@
text-align: center;
}
- /* bg-info is a light cyan — use dark text so it's readable on TV */
+ /* bg-info is a light cyan — use dark text so it's readable on TV */
.badge-xl.bg-info { color: #000 !important; }
.worker-badge {
@@ -457,7 +457,7 @@
@item.Description
@if (item.Colors.Any())
{
- —
+ —
@string.Join(" / ", item.Colors)
}
@@ -626,7 +626,7 @@
});
diff --git a/src/PowderCoating.Web/Views/Jobs/ShopMobile.cshtml b/src/PowderCoating.Web/Views/Jobs/ShopMobile.cshtml
index a03ffc5..1a55ace 100644
--- a/src/PowderCoating.Web/Views/Jobs/ShopMobile.cshtml
+++ b/src/PowderCoating.Web/Views/Jobs/ShopMobile.cshtml
@@ -445,7 +445,7 @@
}
else
{
-
+
}
@@ -608,7 +608,7 @@
btn.disabled = false;
}
} catch (e) {
- showToast('Network error — try again', 'danger');
+ showToast('Network error — try again', 'danger');
btn.innerHTML = originalHtml;
btn.disabled = false;
}
diff --git a/src/PowderCoating.Web/Views/Jobs/StatusBump.cshtml b/src/PowderCoating.Web/Views/Jobs/StatusBump.cshtml
index 2a6996d..ecca943 100644
--- a/src/PowderCoating.Web/Views/Jobs/StatusBump.cshtml
+++ b/src/PowderCoating.Web/Views/Jobs/StatusBump.cshtml
@@ -222,7 +222,7 @@
Due: @job.DueDate.Value.ToString("MMM d, yyyy")
@if (job.DueDate < DateTime.Today && !isTerminal)
{
- — OVERDUE
+ — OVERDUE
}
}
@@ -237,14 +237,14 @@
}
else if (isOnHold)
{
- @* On hold — offer resume (next logical status after resume by advancing) *@
+ @* On hold — offer resume (next logical status after resume by advancing) *@
@if (nextStatus != null)
{
}
diff --git a/src/PowderCoating.Web/Views/Jobs/WorkOrder.cshtml b/src/PowderCoating.Web/Views/Jobs/WorkOrder.cshtml
index 6347b79..4042f30 100644
--- a/src/PowderCoating.Web/Views/Jobs/WorkOrder.cshtml
+++ b/src/PowderCoating.Web/Views/Jobs/WorkOrder.cshtml
@@ -483,10 +483,10 @@
@coat.Sequence
@coat.CoatName
- @(coat.ColorName ?? "—")
- @(coat.ColorCode ?? "—")
- @(coat.Finish ?? "—")
- @(coat.VendorName ?? "—")
+ @(coat.ColorName ?? "—")
+ @(coat.ColorCode ?? "—")
+ @(coat.Finish ?? "—")
+ @(coat.VendorName ?? "—")
@if (coat.PowderToOrder.HasValue && coat.PowderToOrder.Value > 0)
{
@@ -501,7 +501,7 @@
}
else
{
- —
+ —
}
@@ -622,7 +622,7 @@
}
- @* Powder usage QRs — one per unique inventory item *@
+ @* Powder usage QRs — one per unique inventory item *@
@if (hasPowderQrs)
{
@foreach (var pqr in powderQrCodes!)
diff --git a/src/PowderCoating.Web/Views/JobsPriority/Index.cshtml b/src/PowderCoating.Web/Views/JobsPriority/Index.cshtml
index 533a643..853c112 100644
--- a/src/PowderCoating.Web/Views/JobsPriority/Index.cshtml
+++ b/src/PowderCoating.Web/Views/JobsPriority/Index.cshtml
@@ -1,4 +1,4 @@
-@model IEnumerable
+@model IEnumerable
@using PowderCoating.Application.DTOs.Job
@using PowderCoating.Core.Entities
@using PowderCoating.Core.Enums
@@ -51,7 +51,7 @@
- @* ── Carried-Over (Overdue) Jobs ──────────────────────────────────────── *@
+ @* -- Carried-Over (Overdue) Jobs ---------------------------------------- *@
@if (overdueJobs.Any())
{
@@ -59,7 +59,7 @@
}
- @* ── Scheduled Maintenance for the Day ──────────────────────────────── *@
+ @* -- Scheduled Maintenance for the Day -------------------------------- *@