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 <noreply@anthropic.com>
This commit is contained in:
2026-05-20 21:37:10 -04:00
parent 21b39161a3
commit a0bdd2b5b4
252 changed files with 1785 additions and 1633 deletions
@@ -4,7 +4,7 @@
ViewData["Title"] = "Add Inventory Item";
ViewData["PageIcon"] = "bi-box-seam";
ViewData["PageHelpTitle"] = "Add Inventory Item";
ViewData["PageHelpContent"] = "Add a new material to inventory powder coatings, consumables, or other shop supplies. Select a category first to auto-generate a SKU. Use AI Lookup to fill in manufacturer details from a part number. Set Reorder Point and Reorder Quantity so the system can alert you when stock runs low.";
ViewData["PageHelpContent"] = "Add a new material to inventory &mdash; powder coatings, consumables, or other shop supplies. Select a category first to auto-generate a SKU. Use AI Lookup to fill in manufacturer details from a part number. Set Reorder Point and Reorder Quantity so the system can alert you when stock runs low.";
}
<div class="row justify-content-center">
@@ -29,7 +29,7 @@
<a tabindex="0" class="help-icon" role="button"
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
data-bs-title="Basic Information"
data-bs-content="Name and SKU are required. Category drives how the item is filtered and used in quotes choosing a Powder Coating category shows the Coating Specifications section. SKU is auto-generated from the category prefix but you can edit it. Description is optional free text for internal notes.">
data-bs-content="Name and SKU are required. Category drives how the item is filtered and used in quotes &mdash; choosing a Powder Coating category shows the Coating Specifications section. SKU is auto-generated from the category prefix but you can edit it. Description is optional free text for internal notes.">
<i class="bi bi-question-circle"></i>
</a>
</div>
@@ -85,7 +85,7 @@
<a tabindex="0" class="help-icon" role="button"
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
data-bs-title="Product Details"
data-bs-content="Manufacturer, part number, color name, color code, and finish describe the physical product. Use Lookup to auto-fill these fields it checks the product catalog first, then falls back to AI. Coverage is how many sq ft one pound coats at 1 mil thickness (typical: 30). Transfer Efficiency is what percentage of the powder actually sticks (typical: 6070%). Both values are used to calculate Powder Needed on quotes and jobs.">
data-bs-content="Manufacturer, part number, color name, color code, and finish describe the physical product. Use Lookup to auto-fill these fields &mdash; it checks the product catalog first, then falls back to AI. Coverage is how many sq ft one pound coats at 1 mil thickness (typical: 30). Transfer Efficiency is what percentage of the powder actually sticks (typical: 60&ndash;70%). Both values are used to calculate Powder Needed on quotes and jobs.">
<i class="bi bi-question-circle"></i>
</a>
</div>
@@ -129,7 +129,7 @@
<div class="col-md-6">
<label asp-for="SdsUrl" class="form-label">Safety Data Sheet (SDS)</label>
<div class="input-group">
<input asp-for="SdsUrl" class="form-control" id="field-sdsurl" placeholder="https://" />
<input asp-for="SdsUrl" class="form-control" id="field-sdsurl" placeholder="https://&hellip;" />
<a id="field-sdsurl-link" href="@Model.SdsUrl" target="_blank"
class="btn btn-outline-secondary @(string.IsNullOrWhiteSpace(Model.SdsUrl) ? "d-none" : "")" title="Open SDS">
<i class="bi bi-file-earmark-pdf"></i>
@@ -140,7 +140,7 @@
<div class="col-md-6">
<label asp-for="TdsUrl" class="form-label">Technical Data Sheet (TDS)</label>
<div class="input-group">
<input asp-for="TdsUrl" class="form-control" id="field-tdsurl" placeholder="https://" />
<input asp-for="TdsUrl" class="form-control" id="field-tdsurl" placeholder="https://&hellip;" />
<a id="field-tdsurl-link" href="@Model.TdsUrl" target="_blank"
class="btn btn-outline-secondary @(string.IsNullOrWhiteSpace(Model.TdsUrl) ? "d-none" : "")" title="Open TDS">
<i class="bi bi-file-earmark-text"></i>
@@ -193,7 +193,7 @@
<a tabindex="0" class="help-icon" role="button"
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
data-bs-title="Transfer Efficiency"
data-bs-content="The percentage of powder that actually adheres to the part rather than being lost as overspray. Electrostatic spray guns typically achieve 6070%. A lower efficiency means you need to order more powder per job. The system uses this value in the Powder Needed calculation on quotes.">
data-bs-content="The percentage of powder that actually adheres to the part rather than being lost as overspray. Electrostatic spray guns typically achieve 60&ndash;70%. A lower efficiency means you need to order more powder per job. The system uses this value in the Powder Needed calculation on quotes.">
<i class="bi bi-question-circle"></i>
</a>
</div>
@@ -213,7 +213,7 @@
<a tabindex="0" class="help-icon" role="button"
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
data-bs-title="Coating Specifications"
data-bs-content="Cure Temperature and Cure Time come from the manufacturer's tech data sheet they tell the oven operator the correct bake profile. Requires Clear Coat flags powders that need a clear top coat for durability or finish. Color Families tag this powder for filtering and matching in the quote wizard (e.g., a teal powder would get both Green and Blue).">
data-bs-content="Cure Temperature and Cure Time come from the manufacturer's tech data sheet &mdash; they tell the oven operator the correct bake profile. Requires Clear Coat flags powders that need a clear top coat for durability or finish. Color Families tag this powder for filtering and matching in the quote wizard (e.g., a teal powder would get both Green and Blue).">
<i class="bi bi-question-circle"></i>
</a>
</div>
@@ -267,7 +267,7 @@
<a tabindex="0" class="help-icon" role="button"
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
data-bs-title="Stock Information"
data-bs-content="Quantity on Hand is your current starting stock. Reorder Point is the threshold at which the system shows a Low Stock alert when quantity drops to this level it's time to reorder. Reorder Quantity is how much to order in one batch. Location is the shelf or bin label so staff can find the material quickly.">
data-bs-content="Quantity on Hand is your current starting stock. Reorder Point is the threshold at which the system shows a Low Stock alert &mdash; when quantity drops to this level it's time to reorder. Reorder Quantity is how much to order in one batch. Location is the shelf or bin label so staff can find the material quickly.">
<i class="bi bi-question-circle"></i>
</a>
</div>
@@ -293,7 +293,7 @@
<a tabindex="0" class="help-icon" role="button"
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
data-bs-title="Reorder Point"
data-bs-content="When Quantity on Hand falls at or below this number a Low Stock warning appears on the item and in the Inventory summary. Set it high enough to cover your lead time for example if delivery takes a week and you use 2 lb/day, set the reorder point to at least 14 lbs.">
data-bs-content="When Quantity on Hand falls at or below this number a Low Stock warning appears on the item and in the Inventory summary. Set it high enough to cover your lead time &mdash; for example if delivery takes a week and you use 2 lb/day, set the reorder point to at least 14 lbs.">
<i class="bi bi-question-circle"></i>
</a>
</div>
@@ -306,7 +306,7 @@
<a tabindex="0" class="help-icon" role="button"
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
data-bs-title="Reorder Quantity"
data-bs-content="The standard quantity to order when restocking typically a full case or pallet quantity from your supplier. This is informational and appears as a suggested order amount when the item is flagged as low stock.">
data-bs-content="The standard quantity to order when restocking &mdash; typically a full case or pallet quantity from your supplier. This is informational and appears as a suggested order amount when the item is flagged as low stock.">
<i class="bi bi-question-circle"></i>
</a>
</div>
@@ -325,7 +325,7 @@
<a tabindex="0" class="help-icon" role="button"
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
data-bs-title="Pricing &amp; Vendor"
data-bs-content="Unit Cost is what you pay per unit (lb, each, etc.) this is used to calculate total stock value and feeds into job cost calculations. Primary Vendor links to your supplier record for quick reference and purchase ordering.">
data-bs-content="Unit Cost is what you pay per unit (lb, each, etc.) &mdash; this is used to calculate total stock value and feeds into job cost calculations. Primary Vendor links to your supplier record for quick reference and purchase ordering.">
<i class="bi bi-question-circle"></i>
</a>
</div>
@@ -343,7 +343,7 @@
<select asp-for="PrimaryVendorId" class="form-select" id="field-vendor" asp-items="@ViewBag.Vendors"
data-quick-add-url="/Vendors/Create" data-quick-add-title="Add New Vendor">
<option value="">Select vendor</option>
<option value="__new__">+ Add New Vendor</option>
<option value="__new__">+ Add New Vendor&hellip;</option>
</select>
<span asp-validation-for="PrimaryVendorId" class="text-danger"></span>
</div>
@@ -359,7 +359,7 @@
<a tabindex="0" class="help-icon" role="button"
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
data-bs-title="Financial Accounts"
data-bs-content="Inventory Account is the asset account where the value of this stock sits on the balance sheet (e.g., 1200 Inventory Powder). COGS Account is debited when this material is consumed on a job (e.g., 5000 Cost of Goods Sold). Leave blank to use the company defaults set in your Chart of Accounts.">
data-bs-content="Inventory Account is the asset account where the value of this stock sits on the balance sheet (e.g., 1200 Inventory &mdash; Powder). COGS Account is debited when this material is consumed on a job (e.g., 5000 Cost of Goods Sold). Leave blank to use the company defaults set in your Chart of Accounts.">
<i class="bi bi-question-circle"></i>
</a>
</div>
@@ -370,7 +370,7 @@
<select asp-for="InventoryAccountId" class="form-select" asp-items="ViewBag.InventoryAccounts"
data-quick-add-url="/Accounts/Create?preSubType=4" data-quick-add-title="Add Inventory Account">
<option value="">(Default inventory account)</option>
<option value="__new__">+ Add New Account</option>
<option value="__new__">+ Add New Account&hellip;</option>
</select>
<small class="form-text text-muted">Asset account where inventory value is tracked (e.g., 1200 Inventory - Powder).</small>
</div>
@@ -379,7 +379,7 @@
<select asp-for="CogsAccountId" class="form-select" asp-items="ViewBag.CogsAccounts"
data-quick-add-url="/Accounts/Create?preSubType=40" data-quick-add-title="Add COGS Account">
<option value="">(Default COGS account)</option>
<option value="__new__">+ Add New Account</option>
<option value="__new__">+ Add New Account&hellip;</option>
</select>
<small class="form-text text-muted">Expense account debited when this material is consumed on a job.</small>
</div>