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
@@ -26,7 +26,7 @@
</p>
<p>
Keeping inventory accurate matters for two reasons. First, your job and quote pricing is only
as accurate as the unit costs stored in inventory outdated costs lead to under-pricing.
as accurate as the unit costs stored in inventory &mdash; outdated costs lead to under-pricing.
Second, knowing how much powder you have on hand before a job starts prevents the frustrating
situation of running out of material mid-job.
</p>
@@ -45,19 +45,19 @@
<li class="mb-2">
Fill in the item details:
<ul class="mt-1">
<li><strong>Item Name</strong> a clear, descriptive name (e.g., "Gloss Black Powder Tiger Drylac 49/90005").</li>
<li><strong>SKU / Part Number</strong> the manufacturer's part number or your internal SKU.</li>
<li><strong>Category</strong> Powder, Primer, Consumable, Shop Supply, or other category as appropriate.</li>
<li><strong>Unit of Measure</strong> lbs, kg, each, litre, etc.</li>
<li><strong>Unit Cost</strong> your purchase cost per unit. Used in quote and job pricing calculations.</li>
<li><strong>Current Quantity on Hand</strong> the number of units you have right now. This becomes the opening stock level.</li>
<li><strong>Reorder Point</strong> the quantity at which you want to be alerted to reorder. See the Reorder Points section below.</li>
<li><strong>Vendor / Supplier</strong> the vendor you purchase this item from. Linking a vendor lets you quickly see who to call when stock runs low.</li>
<li><strong>Item Name</strong> &mdash; a clear, descriptive name (e.g., "Gloss Black Powder &mdash; Tiger Drylac 49/90005").</li>
<li><strong>SKU / Part Number</strong> &mdash; the manufacturer's part number or your internal SKU.</li>
<li><strong>Category</strong> &mdash; Powder, Primer, Consumable, Shop Supply, or other category as appropriate.</li>
<li><strong>Unit of Measure</strong> &mdash; lbs, kg, each, litre, etc.</li>
<li><strong>Unit Cost</strong> &mdash; your purchase cost per unit. Used in quote and job pricing calculations.</li>
<li><strong>Current Quantity on Hand</strong> &mdash; the number of units you have right now. This becomes the opening stock level.</li>
<li><strong>Reorder Point</strong> &mdash; the quantity at which you want to be alerted to reorder. See the Reorder Points section below.</li>
<li><strong>Vendor / Supplier</strong> &mdash; the vendor you purchase this item from. Linking a vendor lets you quickly see who to call when stock runs low.</li>
</ul>
</li>
<li class="mb-2">
For powder coatings, the <strong>Coverage Rate</strong> (sq ft per lb) and <strong>Transfer Efficiency %</strong>
default to <strong>30 sq ft/lb</strong> and <strong>65%</strong> respectively typical starting values for most powder
default to <strong>30 sq ft/lb</strong> and <strong>65%</strong> respectively &mdash; typical starting values for most powder
and application setups. Adjust these to match your specific powder and equipment. Both values are used when
calculating powder needed on quotes and jobs.
</li>
@@ -86,12 +86,12 @@
<p>
Click the <strong>Lookup</strong> button next to the SKU/Part Number field. Type a color name,
SKU, or part number and the system searches a built-in catalog of thousands of Prismatic Powders
and other manufacturer SKUs. Select a match and the form fills in automatically item name,
and other manufacturer SKUs. Select a match and the form fills in automatically &mdash; item name,
manufacturer, color code, finish, coverage rate, SDS/TDS links, and cure specifications.
</p>
<ul class="mb-3">
<li class="mb-1">The catalog only shows products <strong>not already in your inventory</strong>, preventing duplicates. When editing an existing item, its own catalog entry is always shown.</li>
<li class="mb-1">If no catalog match is found, the lookup falls back to <strong>AI Lookup</strong> Claude searches the web for product specs and fills in whatever it can find.</li>
<li class="mb-1">If no catalog match is found, the lookup falls back to <strong>AI Lookup</strong> &mdash; Claude searches the web for product specs and fills in whatever it can find.</li>
<li class="mb-1">If a vendor name is selected in the Vendor field before searching, results are scoped to that vendor first, then broadened automatically if nothing matches.</li>
</ul>
@@ -102,11 +102,11 @@
The scanner reads the code and attempts to identify the product:
</p>
<ol class="mb-3">
<li class="mb-1">If the QR code matches a product in the platform catalog, the form fills in automatically same as a manual catalog lookup.</li>
<li class="mb-1">If the QR code matches a product in the platform catalog, the form fills in automatically &mdash; same as a manual catalog lookup.</li>
<li class="mb-1">If no catalog match is found, the AI analyzes the label image and fills in whatever details it can extract (color name, SKU, manufacturer, finish).</li>
<li class="mb-1">
If the scanned product is <strong>already in your inventory</strong>, a prompt appears to
<strong>Add Stock</strong> to the existing item instead enter the quantity received and an
<strong>Add Stock</strong> to the existing item instead &mdash; enter the quantity received and an
optional updated unit cost, then save. No duplicate item is created.
</li>
</ol>
@@ -127,7 +127,7 @@
</h2>
<p>
The <strong>quantity on hand</strong> for each item is updated automatically whenever a transaction
is recorded a purchase receipt increases stock, a job consumption decreases it, and a manual
is recorded &mdash; a purchase receipt increases stock, a job consumption decreases it, and a manual
adjustment sets it to the corrected count.
</p>
<p>
@@ -142,8 +142,8 @@
A good reorder point accounts for two factors:
</p>
<ul class="mb-3">
<li class="mb-1"><strong>Lead time</strong> how many days it typically takes for your vendor to deliver after you place an order. If lead time is 5 days, your reorder point should cover at least 5 days of usage.</li>
<li class="mb-1"><strong>Daily usage rate</strong> how much of the item you typically consume per day based on your job volume.</li>
<li class="mb-1"><strong>Lead time</strong> &mdash; how many days it typically takes for your vendor to deliver after you place an order. If lead time is 5 days, your reorder point should cover at least 5 days of usage.</li>
<li class="mb-1"><strong>Daily usage rate</strong> &mdash; how much of the item you typically consume per day based on your job volume.</li>
</ul>
<p>
For example, if you use 3 lbs of a powder per day and your vendor takes 5 days to deliver, a
@@ -163,9 +163,9 @@
</p>
<p>Click <strong>Stock Adjustment</strong> in the Actions panel and choose one of three modes:</p>
<ul class="mb-3">
<li class="mb-2"><strong>Add Stock</strong> increases the current quantity by the amount you enter. Use for received goods, returns, or found stock.</li>
<li class="mb-2"><strong>Remove Stock</strong> decreases the current quantity by the amount you enter. Use for waste, spillage, or damage write-offs.</li>
<li class="mb-2"><strong>Set Exact</strong> sets the quantity on hand to the exact number you enter, regardless of the current value. Use after a physical inventory count to correct the balance.</li>
<li class="mb-2"><strong>Add Stock</strong> &mdash; increases the current quantity by the amount you enter. Use for received goods, returns, or found stock.</li>
<li class="mb-2"><strong>Remove Stock</strong> &mdash; decreases the current quantity by the amount you enter. Use for waste, spillage, or damage write-offs.</li>
<li class="mb-2"><strong>Set Exact</strong> &mdash; sets the quantity on hand to the exact number you enter, regardless of the current value. Use after a physical inventory count to correct the balance.</li>
</ul>
<p>
A <strong>reason</strong> is required for every adjustment. Common reasons are listed in the dropdown
@@ -230,7 +230,7 @@
</tr>
<tr>
<td><strong>Job Usage</strong></td>
<td>Powder consumed during a job recorded automatically when actual usage is entered on a job coat.</td>
<td>Powder consumed during a job &mdash; recorded automatically when actual usage is entered on a job coat.</td>
<td class="text-danger fw-semibold">&minus; Decreases</td>
</tr>
<tr>
@@ -268,12 +268,12 @@
<p>It has two tabs:</p>
<ul class="mb-3">
<li class="mb-2">
<strong>Stock Transactions</strong> every transaction recorded against your inventory items,
<strong>Stock Transactions</strong> &mdash; every transaction recorded against your inventory items,
showing date, type, quantity (green for additions, red for deductions), unit cost, total cost,
running balance after the transaction, and a link to the source Purchase Order if applicable.
</li>
<li class="mb-2">
<strong>Powder Usage by Job</strong> every instance of powder being consumed on a job coat,
<strong>Powder Usage by Job</strong> &mdash; every instance of powder being consumed on a job coat,
showing the job number (linked to the job), customer, color applied, estimated vs actual pounds
used, and the variance. A totals row at the bottom summarises the full filtered selection.
</li>
@@ -287,7 +287,7 @@
<i class="bi bi-lightbulb-fill flex-shrink-0 mt-1"></i>
<div>
To see the history for a single powder, open its Details page and click
<strong>View Activity History</strong> the Inventory Activity page will open pre-filtered
<strong>View Activity History</strong> &mdash; the Inventory Activity page will open pre-filtered
to that item.
</div>
</div>
@@ -299,14 +299,14 @@
</h2>
<p>
Every inventory item has a printable <strong>QR code label</strong>. Stick it on the bag, bin,
or shelf and shop floor workers can scan it with their phone to log how much they used
or shelf and shop floor workers can scan it with their phone to log how much they used &mdash;
without ever touching a desktop.
</p>
<h3 class="h6 fw-semibold mt-4 mb-2">Printing a label</h3>
<ol class="mb-3">
<li class="mb-1">Open the inventory item's Details page.</li>
<li class="mb-1">Click <strong>Print QR Label</strong> in the Actions panel the label opens in a new tab.</li>
<li class="mb-1">Click <strong>Print QR Label</strong> in the Actions panel &mdash; the label opens in a new tab.</li>
<li class="mb-1">Click <strong>Print Label</strong> and send it to your printer. The label is sized for a standard 3.5&Prime; label and includes the item name, SKU, colour, finish, and manufacturer.</li>
</ol>
@@ -316,9 +316,9 @@
<li class="mb-1">
<strong>Select a job</strong> (optional but recommended):
<ul class="mt-1">
<li><em>My Jobs</em> active jobs assigned to your account appear first.</li>
<li><em>Other Jobs</em> any other open job in the system.</li>
<li><em>No Job</em> log usage without a job reference (e.g. a waste event).</li>
<li><em>My Jobs</em> &mdash; active jobs assigned to your account appear first.</li>
<li><em>Other Jobs</em> &mdash; any other open job in the system.</li>
<li><em>No Job</em> &mdash; log usage without a job reference (e.g. a waste event).</li>
</ul>
</li>
<li class="mb-1">Enter the <strong>quantity</strong> used. A live preview shows what the new stock balance will be.</li>
@@ -331,8 +331,8 @@
The success screen gives you two options:
</p>
<ul class="mb-3">
<li class="mb-1"><strong>Log Another Item for This Job</strong> returns to the scan page with the same job pre-selected, so you can quickly log the next powder without re-picking the job.</li>
<li class="mb-1"><strong>Back to Inventory</strong> or <strong>View Item Details</strong> returns to a neutral state.</li>
<li class="mb-1"><strong>Log Another Item for This Job</strong> &mdash; returns to the scan page with the same job pre-selected, so you can quickly log the next powder without re-picking the job.</li>
<li class="mb-1"><strong>Back to Inventory</strong> or <strong>View Item Details</strong> &mdash; returns to a neutral state.</li>
</ul>
<div class="alert alert-permanent alert-info d-flex gap-2 mb-0" role="alert">
@@ -341,7 +341,7 @@
Every scan-based usage log is recorded as a <strong>JobUsage</strong> or <strong>Adjustment</strong>
transaction and immediately reduces the item's quantity on hand. You can review it on the
<a href="/Inventory/Ledger" class="alert-link">Inventory Activity</a> page.
The first time a worker scans on a new device they will be asked to log in after that the
The first time a worker scans on a new device they will be asked to log in &mdash; after that the
browser keeps them signed in.
</div>
</div>
@@ -374,7 +374,7 @@
<div>
An item continues to show as Low Stock or Out of Stock even after you have placed a Purchase
Order, until the goods are physically received and the PO is marked as Received in the system.
This is intentional it reminds you that stock has not yet arrived.
This is intentional &mdash; it reminds you that stock has not yet arrived.
</div>
</div>
</section>
@@ -428,12 +428,12 @@
<li class="mb-2">Go to <strong><a asp-controller="CompanySettings" asp-action="Index">Company Settings</a> &rsaquo; Data Lookups &rsaquo; Inventory Categories</strong>.</li>
<li class="mb-2">Find the category that contains your powder coating colors (e.g. "Powder Coatings").</li>
<li class="mb-2">Click the edit icon and check the <strong>Is Coating</strong> checkbox.</li>
<li class="mb-2">Save. Items in that category will immediately appear in the powder color dropdown on all quotes and jobs no restart required.</li>
<li class="mb-2">Save. Items in that category will immediately appear in the powder color dropdown on all quotes and jobs &mdash; no restart required.</li>
</ol>
<h3 class="h6 fw-semibold mt-3 mb-2">Which categories should have "Is Coating" enabled?</h3>
<p>
Only categories that contain actual powder coating colors the materials that go into the oven
Only categories that contain actual powder coating colors &mdash; the materials that go into the oven
and bond to the part. Do <strong>not</strong> enable this on categories for primers, masking
supplies, consumables, or equipment. Enabling it on non-coating categories will pollute the
color dropdown with irrelevant items and make it harder to find the right powder.
@@ -443,7 +443,7 @@
<i class="bi bi-lightbulb-fill flex-shrink-0 mt-1"></i>
<div>
The "Is Coating" flag also controls where the <strong>sample panel toggle</strong> appears
on an item's Details page. The toggle "I have a swatch/sample of this color" only
on an item's Details page. The toggle &mdash; "I have a swatch/sample of this color" &mdash; only
shows up for items in a coating category, and those items are the ones tracked on the
<strong>Sample Panels</strong> page.
</div>
@@ -459,9 +459,9 @@
will be needed before the job begins. This estimate is based on three values:
</p>
<ul class="mb-3">
<li class="mb-1"><strong>Surface area</strong> the total square footage to be coated (entered per item in the job or quote wizard).</li>
<li class="mb-1"><strong>Coverage rate</strong> how many square feet one pound of the selected powder covers (set on the inventory item).</li>
<li class="mb-1"><strong>Number of coats</strong> selected when you add the coating to the item.</li>
<li class="mb-1"><strong>Surface area</strong> &mdash; the total square footage to be coated (entered per item in the job or quote wizard).</li>
<li class="mb-1"><strong>Coverage rate</strong> &mdash; how many square feet one pound of the selected powder covers (set on the inventory item).</li>
<li class="mb-1"><strong>Number of coats</strong> &mdash; selected when you add the coating to the item.</li>
</ul>
<p>
The <strong>Powder Needed</strong> figure appears in the item wizard as you build the quote or job,
@@ -486,30 +486,30 @@
The AI Price Check reviews every active, priced item in your
<a asp-controller="CatalogItems" asp-action="Index">Catalog Items</a> list against your
shop's actual operating costs. It estimates a realistic surface area and processing time
for each item, calculates a cost floor, and compares that to your current price flagging
for each item, calculates a cost floor, and compares that to your current price &mdash; flagging
anything that may be losing money, leaving margin on the table, or priced above market rates.
</p>
<h3 class="h6 fw-semibold mt-3 mb-2">Verdicts</h3>
<ul class="mb-3">
<li class="mb-2"><strong>Below Cost</strong> price is at or below the estimated cost floor. The shop loses money on every sale of this item.</li>
<li class="mb-2"><strong>Thin Margin</strong> price covers costs but falls below your target margin percentage.</li>
<li class="mb-2"><strong>High</strong> price appears significantly above typical market rates, which may cost you work.</li>
<li class="mb-2"><strong>OK</strong> price is within a reasonable range given your costs and market context.</li>
<li class="mb-2"><strong>Below Cost</strong> &mdash; price is at or below the estimated cost floor. The shop loses money on every sale of this item.</li>
<li class="mb-2"><strong>Thin Margin</strong> &mdash; price covers costs but falls below your target margin percentage.</li>
<li class="mb-2"><strong>High</strong> &mdash; price appears significantly above typical market rates, which may cost you work.</li>
<li class="mb-2"><strong>OK</strong> &mdash; price is within a reasonable range given your costs and market context.</li>
</ul>
<h3 class="h6 fw-semibold mt-3 mb-2">How to run it</h3>
<ol class="mb-3">
<li class="mb-2">Make sure your <a asp-controller="CompanySettings" asp-action="Index">operating costs</a> are up to date stale rates produce inaccurate verdicts.</li>
<li class="mb-2">Make sure your <a asp-controller="CompanySettings" asp-action="Index">operating costs</a> are up to date &mdash; stale rates produce inaccurate verdicts.</li>
<li class="mb-2">Go to <a asp-controller="CatalogItems" asp-action="Index">Catalog Items</a> and click <strong>AI Price Check</strong> in the top-right.</li>
<li class="mb-2">Click <strong>Analyze Catalog with AI</strong>. A progress overlay appears while the analysis runs (allow 710 minutes for large catalogs).</li>
<li class="mb-2">Review results sorted by severity Below Cost items appear first. Click <strong>Edit Price</strong> on any item to update it directly from the results page.</li>
<li class="mb-2">Click <strong>Analyze Catalog with AI</strong>. A progress overlay appears while the analysis runs (allow 7&ndash;10 minutes for large catalogs).</li>
<li class="mb-2">Review results sorted by severity &mdash; Below Cost items appear first. Click <strong>Edit Price</strong> on any item to update it directly from the results page.</li>
</ol>
<h3 class="h6 fw-semibold mt-3 mb-2">Things to know</h3>
<ul class="mb-3">
<li class="mb-2"><strong>Run limit:</strong> Analysis can be run once per quarter (90 days). The button shows the next available date when a recent run exists.</li>
<li class="mb-2"><strong>Confidence levels:</strong> Each result shows High, Medium, or Low confidence. Vague item names like "Custom Part" will be Low verify those manually.</li>
<li class="mb-2"><strong>Confidence levels:</strong> Each result shows High, Medium, or Low confidence. Vague item names like "Custom Part" will be Low &mdash; verify those manually.</li>
<li class="mb-2"><strong>Category paths matter:</strong> The AI uses the full category path (e.g. "Cerakote &rsaquo; Firearms") to determine the coating type. Make sure specialty items are in the correct category.</li>
<li class="mb-2"><strong>$0 items skipped:</strong> Placeholder items and category headers with no price are automatically excluded from analysis.</li>
</ul>
@@ -518,7 +518,7 @@
<i class="bi bi-exclamation-triangle-fill flex-shrink-0 mt-1"></i>
<div>
Results are estimates based on industry knowledge and your shop's rates. Always apply
your own judgment before changing prices especially for items flagged as Low confidence.
your own judgment before changing prices &mdash; especially for items flagged as Low confidence.
</div>
</div>
</section>