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:
@@ -3,7 +3,7 @@
|
||||
ViewData["Title"] = "AI Catalog Price Check";
|
||||
ViewData["PageIcon"] = "bi-robot";
|
||||
ViewData["PageHelpTitle"] = "AI Catalog Price Check";
|
||||
ViewData["PageHelpContent"] = "The AI Price Check reviews every item in your catalog against your actual operating costs and flags items that may be priced below cost, have thin margins, or appear unusually high. Results are estimates based on industry knowledge and your shop's rates — always apply your own judgment before changing prices.";
|
||||
ViewData["PageHelpContent"] = "The AI Price Check reviews every item in your catalog against your actual operating costs and flags items that may be priced below cost, have thin margins, or appear unusually high. Results are estimates based on industry knowledge and your shop's rates — always apply your own judgment before changing prices.";
|
||||
|
||||
var sortedResults = Model?.Results
|
||||
.OrderBy(r => r.Verdict switch
|
||||
@@ -82,7 +82,7 @@
|
||||
<div class="progress-card">
|
||||
<div class="icon"><i class="bi bi-robot"></i></div>
|
||||
<h5>Analyzing your catalog</h5>
|
||||
<p class="status-msg" id="overlay-status">Preparing items…</p>
|
||||
<p class="status-msg" id="overlay-status">Preparing items…</p>
|
||||
<div class="progress-bar-track">
|
||||
<div class="progress-bar-fill" id="overlay-bar"></div>
|
||||
</div>
|
||||
@@ -151,22 +151,22 @@
|
||||
<div>
|
||||
<h6 class="fw-semibold mb-1">What this analysis does</h6>
|
||||
<p class="small text-muted mb-2">
|
||||
Our AI system reviews every active, priced item in your catalog against your shop's actual operating costs —
|
||||
Our AI system reviews every active, priced item in your catalog against your shop's actual operating costs —
|
||||
labor, oven time, sandblasting, coating booth, and powder material. For each item it estimates a
|
||||
realistic surface area and processing time, calculates a cost floor, then compares that to your
|
||||
current price and returns one of four verdicts:
|
||||
</p>
|
||||
<div class="d-flex flex-wrap gap-2 mb-2">
|
||||
<span class="verdict-badge verdict-below-cost">Below Cost</span><span class="small text-muted align-self-center">— you're losing money on this item</span>
|
||||
<span class="verdict-badge verdict-below-cost">Below Cost</span><span class="small text-muted align-self-center">— you're losing money on this item</span>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap gap-2 mb-2">
|
||||
<span class="verdict-badge verdict-low">Thin Margin</span><span class="small text-muted align-self-center">— above cost floor but below your target margin</span>
|
||||
<span class="verdict-badge verdict-low">Thin Margin</span><span class="small text-muted align-self-center">— above cost floor but below your target margin</span>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap gap-2 mb-2">
|
||||
<span class="verdict-badge verdict-high">High</span><span class="small text-muted align-self-center">— significantly above typical market rates</span>
|
||||
<span class="verdict-badge verdict-high">High</span><span class="small text-muted align-self-center">— significantly above typical market rates</span>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap gap-2 mb-3">
|
||||
<span class="verdict-badge verdict-ok">OK</span><span class="small text-muted align-self-center">— price is within a reasonable range</span>
|
||||
<span class="verdict-badge verdict-ok">OK</span><span class="small text-muted align-self-center">— price is within a reasonable range</span>
|
||||
</div>
|
||||
<p class="small text-muted mb-0">
|
||||
<i class="bi bi-exclamation-triangle me-1 text-warning"></i>
|
||||
@@ -301,15 +301,15 @@ else
|
||||
<div class="col-4 text-center">
|
||||
<div class="small text-muted">Suggested</div>
|
||||
<div class="fw-semibold text-primary">
|
||||
@item.SuggestedPriceMin.ToString("C") – @item.SuggestedPriceMax.ToString("C")
|
||||
@item.SuggestedPriceMin.ToString("C") – @item.SuggestedPriceMax.ToString("C")
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="small text-muted mb-1">
|
||||
<i class="bi bi-rulers me-1"></i>
|
||||
Est. @item.EstimatedSqFtMin–@item.EstimatedSqFtMax sqft •
|
||||
@item.EstimatedMinutesMin–@item.EstimatedMinutesMax min
|
||||
Est. @item.EstimatedSqFtMin–@item.EstimatedSqFtMax sqft •
|
||||
@item.EstimatedMinutesMin–@item.EstimatedMinutesMax min
|
||||
</div>
|
||||
|
||||
<p class="small mb-1">@item.Reasoning</p>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<div class="alert alert-permanent alert-warning d-flex gap-2 mb-4" role="alert">
|
||||
<i class="bi bi-exclamation-triangle-fill flex-shrink-0 mt-1"></i>
|
||||
<div>
|
||||
<strong>Catalog item prices are fixed.</strong> The price you enter here is exactly what gets charged when this item is added to a quote or job — no markup, no prep service charges, and no complexity adjustments are added on top. Make sure your price already includes labor, materials, and margin.
|
||||
<strong>Catalog item prices are fixed.</strong> The price you enter here is exactly what gets charged when this item is added to a quote or job — no markup, no prep service charges, and no complexity adjustments are added on top. Make sure your price already includes labor, materials, and margin.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
<a tabindex="0" class="help-icon" role="button"
|
||||
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
|
||||
data-bs-title="Item Name"
|
||||
data-bs-content="Use a specific, recognizable name. The name appears on quotes, invoices, and in the picker dropdown. Good names include material and size where relevant — e.g., 'Steel Bracket (6in)', '18in Alloy Wheel'.">
|
||||
data-bs-content="Use a specific, recognizable name. The name appears on quotes, invoices, and in the picker dropdown. Good names include material and size where relevant — e.g., 'Steel Bracket (6in)', '18in Alloy Wheel'.">
|
||||
<i class="bi bi-question-circle"></i>
|
||||
</a>
|
||||
</div>
|
||||
@@ -75,7 +75,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"
|
||||
data-bs-content="The Default Price is the exact amount charged when this item is added to a quote — no markup, prep services, or complexity adjustments are applied on top. Set the all-in price you want to bill. Approximate Area is optional — if set, it helps estimate powder needed for reporting purposes.">
|
||||
data-bs-content="The Default Price is the exact amount charged when this item is added to a quote — no markup, prep services, or complexity adjustments are applied on top. Set the all-in price you want to bill. Approximate Area is optional — if set, it helps estimate powder needed for reporting purposes.">
|
||||
<i class="bi bi-question-circle"></i>
|
||||
</a>
|
||||
</div>
|
||||
@@ -87,7 +87,7 @@
|
||||
<a tabindex="0" class="help-icon" role="button"
|
||||
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
|
||||
data-bs-title="Default Price"
|
||||
data-bs-content="This is the final price charged to the customer — no markup, prep services, or complexity charges are added on top. Enter the all-in amount you want to bill for this item. Staff can still override it on individual quotes if needed.">
|
||||
data-bs-content="This is the final price charged to the customer — no markup, prep services, or complexity charges are added on top. Enter the all-in amount you want to bill for this item. Staff can still override it on individual quotes if needed.">
|
||||
<i class="bi bi-question-circle"></i>
|
||||
</a>
|
||||
</div>
|
||||
@@ -122,7 +122,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="Links this item to your chart of accounts for accounting exports. Revenue Account is credited when invoiced; COGS Account is debited when materials are consumed. Leave both blank to use the company default accounts — most shops only need to set these for items with special accounting treatment.">
|
||||
data-bs-content="Links this item to your chart of accounts for accounting exports. Revenue Account is credited when invoiced; COGS Account is debited when materials are consumed. Leave both blank to use the company default accounts — most shops only need to set these for items with special accounting treatment.">
|
||||
<i class="bi bi-question-circle"></i>
|
||||
</a>
|
||||
</div>
|
||||
@@ -140,7 +140,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…</option>
|
||||
</select>
|
||||
<small class="form-text text-muted">Account debited when materials are consumed.</small>
|
||||
</div>
|
||||
@@ -164,7 +164,7 @@
|
||||
<div class="mb-3">
|
||||
<label for="image" class="form-label fw-semibold">Upload Image</label>
|
||||
<input type="file" class="form-control" id="image" name="image" accept="image/jpeg,image/png,image/gif,image/webp" onchange="previewCatalogImage(this)" />
|
||||
<div class="form-text">Accepted formats: jpg, jpeg, png, gif, webp — max 10 MB. A 200×200 thumbnail is generated automatically.</div>
|
||||
<div class="form-text">Accepted formats: jpg, jpeg, png, gif, webp — max 10 MB. A 200×200 thumbnail is generated automatically.</div>
|
||||
<div id="imagePreview" class="mt-2 d-none">
|
||||
<img id="imagePreviewImg" src="" alt="Preview" style="max-width:200px;max-height:200px;object-fit:contain;border:1px solid #dee2e6;border-radius:6px;" />
|
||||
</div>
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
<a tabindex="0" class="help-icon" role="button"
|
||||
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
|
||||
data-bs-title="Item Name"
|
||||
data-bs-content="Use a specific, recognizable name. The name appears on quotes, invoices, and in the picker dropdown. Good names include material and size where relevant — e.g., 'Steel Bracket (6in)', '18in Alloy Wheel'.">
|
||||
data-bs-content="Use a specific, recognizable name. The name appears on quotes, invoices, and in the picker dropdown. Good names include material and size where relevant — e.g., 'Steel Bracket (6in)', '18in Alloy Wheel'.">
|
||||
<i class="bi bi-question-circle"></i>
|
||||
</a>
|
||||
</div>
|
||||
@@ -69,7 +69,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 & Status"
|
||||
data-bs-content="The Default Price is the exact amount charged when this item is added to a quote — no markup, prep services, or complexity adjustments are applied on top. Staff can override it on individual quotes. Set Status to Inactive to hide the item from the picker without deleting it.">
|
||||
data-bs-content="The Default Price is the exact amount charged when this item is added to a quote — no markup, prep services, or complexity adjustments are applied on top. Staff can override it on individual quotes. Set Status to Inactive to hide the item from the picker without deleting it.">
|
||||
<i class="bi bi-question-circle"></i>
|
||||
</a>
|
||||
</div>
|
||||
@@ -81,7 +81,7 @@
|
||||
<a tabindex="0" class="help-icon" role="button"
|
||||
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
|
||||
data-bs-title="Default Price"
|
||||
data-bs-content="This is the final price charged to the customer — no markup, prep services, or complexity charges are added on top. Enter the all-in amount you want to bill for this item. Staff can still override it on individual quotes if needed.">
|
||||
data-bs-content="This is the final price charged to the customer — no markup, prep services, or complexity charges are added on top. Enter the all-in amount you want to bill for this item. Staff can still override it on individual quotes if needed.">
|
||||
<i class="bi bi-question-circle"></i>
|
||||
</a>
|
||||
</div>
|
||||
@@ -125,7 +125,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="Links this item to your chart of accounts for accounting exports. Revenue Account is credited when invoiced; COGS Account is debited when materials are consumed. Leave both blank to use the company default accounts — most shops only need to set these for items with special accounting treatment.">
|
||||
data-bs-content="Links this item to your chart of accounts for accounting exports. Revenue Account is credited when invoiced; COGS Account is debited when materials are consumed. Leave both blank to use the company default accounts — most shops only need to set these for items with special accounting treatment.">
|
||||
<i class="bi bi-question-circle"></i>
|
||||
</a>
|
||||
</div>
|
||||
@@ -143,7 +143,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…</option>
|
||||
</select>
|
||||
<small class="form-text text-muted">Account debited when materials are consumed.</small>
|
||||
</div>
|
||||
@@ -186,7 +186,7 @@
|
||||
<div class="mb-3">
|
||||
<label for="image" class="form-label fw-semibold">Upload Image</label>
|
||||
<input type="file" class="form-control" id="image" name="image" accept="image/jpeg,image/png,image/gif,image/webp" onchange="previewCatalogImage(this)" />
|
||||
<div class="form-text">Accepted formats: jpg, jpeg, png, gif, webp — max 10 MB. A 200×200 thumbnail is generated automatically.</div>
|
||||
<div class="form-text">Accepted formats: jpg, jpeg, png, gif, webp — max 10 MB. A 200×200 thumbnail is generated automatically.</div>
|
||||
<div id="imagePreview" class="mt-2 d-none">
|
||||
<img id="imagePreviewImg" src="" alt="Preview" style="max-width:200px;max-height:200px;object-fit:contain;border:1px solid #dee2e6;border-radius:6px;" />
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
ViewData["Title"] = "Product Catalog";
|
||||
ViewData["PageIcon"] = "bi-book";
|
||||
ViewData["PageHelpTitle"] = "Product Catalog";
|
||||
ViewData["PageHelpContent"] = "The Product Catalog is a library of standard items (wheels, brackets, panels, etc.) that your shop regularly quotes and invoices. Each item has a fixed price — when a catalog item is added to a quote or job, that price is used exactly as entered. No markup, no prep services, and no complexity charges are added on top. Organize items into categories to keep the catalog easy to browse.";
|
||||
ViewData["PageHelpContent"] = "The Product Catalog is a library of standard items (wheels, brackets, panels, etc.) that your shop regularly quotes and invoices. Each item has a fixed price — when a catalog item is added to a quote or job, that price is used exactly as entered. No markup, no prep services, and no complexity charges are added on top. Organize items into categories to keep the catalog easy to browse.";
|
||||
var totalItemsCount = ViewBag.TotalItemsCount ?? 0;
|
||||
var activeItemsCount = ViewBag.ActiveItemsCount ?? 0;
|
||||
var averagePrice = ViewBag.AveragePrice ?? 0m;
|
||||
|
||||
Reference in New Issue
Block a user