Add Custom Powder Order line item and fix CSV import FinalPrice crash
Custom powder/incoming powder material cost now flows into a separate auto-generated 'Custom Powder Order' line item instead of rolling into individual item prices, so users can add shipping charges before the customer sees the total. A dashed yellow preview card in the wizard shows the material cost and lets users edit the total (including shipping) before saving. After first save the price is user-owned. Also fixes a fatal CSV import crash when FinalPrice contains a non-numeric value (e.g. 'false' from a spreadsheet formula): the job CSV importer now streams rows one at a time with a lenient decimal converter, treating bad values as $0 with a per-row warning instead of aborting the entire import. Updated HelpKnowledgeBase.cs and Help articles (Jobs, Quotes) with Custom Powder Order behavior and a new Data Import / Export section. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -273,6 +273,29 @@
|
||||
appear as sub-lines under the item on the job details page.
|
||||
</p>
|
||||
|
||||
<h3 class="h6 fw-semibold mt-4 mb-2"><i class="bi bi-truck me-1 text-warning"></i>Custom Powder Order</h3>
|
||||
<p>
|
||||
When a coat uses a <strong>custom powder</strong> (manually entered cost per lb, no inventory item
|
||||
selected) or an <strong>incoming powder</strong> (a color being ordered that is not yet in stock),
|
||||
the material cost is separated from the item price and collected into a single
|
||||
<strong>Custom Powder Order</strong> line item. This keeps per-item pricing clean and lets you
|
||||
add shipping or freight before the customer sees the total.
|
||||
</p>
|
||||
<ul class="mb-3">
|
||||
<li class="mb-2">
|
||||
While building the job, a dashed yellow <strong>Powder Order</strong> preview card appears
|
||||
below the item cards. Edit the price to include any shipping before saving.
|
||||
</li>
|
||||
<li class="mb-2">
|
||||
On the saved job, the Custom Powder Order appears as its own line item with the ordered
|
||||
color name(s) in its description.
|
||||
</li>
|
||||
<li>
|
||||
The Custom Powder Order is created once on first save. The price is user-owned after that
|
||||
and will not be overwritten by the system on subsequent saves.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h3 class="h6 fw-semibold mt-4 mb-2">Save to Product Catalog</h3>
|
||||
<p>
|
||||
After completing the coatings and prep services steps, <strong>Calculated</strong> and
|
||||
|
||||
@@ -170,6 +170,35 @@
|
||||
prep services — sandblasting, masking, and/or cleaning — that will be performed before coating.
|
||||
</p>
|
||||
|
||||
<h3 class="h6 fw-semibold mt-4 mb-2"><i class="bi bi-truck me-1 text-warning"></i>Custom Powder Order</h3>
|
||||
<p>
|
||||
When a coat uses a <strong>custom powder</strong> (you enter a cost per lb manually without selecting
|
||||
an inventory item) or an <strong>incoming powder</strong> (a color you need to order that is not yet
|
||||
in stock), the powder material cost is <strong>not</strong> added to the individual item price.
|
||||
Instead, the system auto-generates a separate <strong>Custom Powder Order</strong> line item that
|
||||
collects all ordering costs in one place — so you can include shipping and freight before
|
||||
presenting the quote to the customer.
|
||||
</p>
|
||||
<ul class="mb-3">
|
||||
<li class="mb-2">
|
||||
While building the quote, a dashed yellow <strong>Powder Order</strong> preview card appears
|
||||
below the item cards showing the calculated material cost. The price field is editable —
|
||||
type the total you want to charge (material + any shipping) before saving.
|
||||
</li>
|
||||
<li class="mb-2">
|
||||
On the saved quote, the Custom Powder Order appears as its own line item with the color
|
||||
name(s) in the description (e.g. <em>Custom Powder Order (Gloss Black, Satin Silver)</em>).
|
||||
</li>
|
||||
<li class="mb-2">
|
||||
A yellow banner on the Quote Details page reminds you when a Custom Powder Order is present
|
||||
so you don’t forget to confirm the shipping amount.
|
||||
</li>
|
||||
<li>
|
||||
The Custom Powder Order is created <strong>once</strong> on first save. After that the price
|
||||
is yours — the system will not overwrite it on subsequent saves.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h3 class="h6 fw-semibold mt-3 mb-2">Save to Product Catalog</h3>
|
||||
<p>
|
||||
After completing the prep services step, Calculated and AI Photo items display one final step:
|
||||
|
||||
@@ -216,6 +216,28 @@
|
||||
<p class="small">Click <strong>Add Item</strong> to get started.</p>
|
||||
</div>
|
||||
<div id="itemCardsContainer"></div>
|
||||
<div id="customPowderOrderPreview" class="d-none mt-2">
|
||||
<div class="quote-item-card border-warning" style="border-style:dashed!important;background:var(--bs-warning-bg-subtle,#fff8e1);">
|
||||
<div class="d-flex align-items-start gap-2">
|
||||
<div class="flex-grow-1">
|
||||
<div class="d-flex align-items-center flex-wrap gap-1 mb-1">
|
||||
<span class="badge bg-warning text-dark item-badge"><i class="bi bi-truck me-1"></i>Powder Order</span>
|
||||
<span class="fw-semibold" id="customPowderOrderPreviewDesc">Custom Powder Order</span>
|
||||
</div>
|
||||
<div class="text-muted small"><span id="customPowderOrderPreviewPrice"></span> — edit total to include shipping</div>
|
||||
</div>
|
||||
<div class="text-center flex-shrink-0" style="min-width:45px;">1</div>
|
||||
<div class="flex-shrink-0" style="min-width:90px;">
|
||||
<input type="number" id="customPowderOrderPriceInput"
|
||||
class="form-control form-control-sm text-end"
|
||||
min="0" step="0.01" placeholder="0.00"
|
||||
title="Enter total including any shipping"
|
||||
oninput="onCustomPowderPriceEdit(this.value)">
|
||||
</div>
|
||||
<div style="min-width:66px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="hiddenFieldsContainer"></div>
|
||||
<div id="aiPhotoTempIdsContainer"></div>
|
||||
</div>
|
||||
@@ -411,7 +433,7 @@
|
||||
sequence = c.Sequence,
|
||||
inventoryItemId = c.InventoryItemId,
|
||||
colorName = c.ColorName,
|
||||
vendorId = c.VendorId,
|
||||
supplierId = c.VendorId,
|
||||
colorCode = c.ColorCode,
|
||||
finish = c.Finish,
|
||||
coverageSqFtPerLb = c.CoverageSqFtPerLb,
|
||||
|
||||
@@ -326,6 +326,13 @@
|
||||
<i class="bi bi-plus-circle me-1"></i>Add Item
|
||||
</button>
|
||||
</div>
|
||||
@if (Model.Items.Any(i => i.Description != null && i.Description.StartsWith("Custom Powder Order")))
|
||||
{
|
||||
<div class="alert alert-warning alert-permanent mx-3 mt-3 mb-0" role="alert">
|
||||
<i class="bi bi-truck me-2"></i><strong>Custom Powder Order</strong> —
|
||||
The line item below shows material cost only. Remember to add shipping before invoicing this job.
|
||||
</div>
|
||||
}
|
||||
@if (Model.Items.Any())
|
||||
{
|
||||
var allItems = Model.Items.ToList();
|
||||
|
||||
@@ -185,6 +185,28 @@
|
||||
<p class="small">Click <strong>Add Item</strong> to get started.</p>
|
||||
</div>
|
||||
<div id="itemCardsContainer"></div>
|
||||
<div id="customPowderOrderPreview" class="d-none mt-2">
|
||||
<div class="quote-item-card border-warning" style="border-style:dashed!important;background:var(--bs-warning-bg-subtle,#fff8e1);">
|
||||
<div class="d-flex align-items-start gap-2">
|
||||
<div class="flex-grow-1">
|
||||
<div class="d-flex align-items-center flex-wrap gap-1 mb-1">
|
||||
<span class="badge bg-warning text-dark item-badge"><i class="bi bi-truck me-1"></i>Powder Order</span>
|
||||
<span class="fw-semibold" id="customPowderOrderPreviewDesc">Custom Powder Order</span>
|
||||
</div>
|
||||
<div class="text-muted small"><span id="customPowderOrderPreviewPrice"></span> — edit total to include shipping</div>
|
||||
</div>
|
||||
<div class="text-center flex-shrink-0" style="min-width:45px;">1</div>
|
||||
<div class="flex-shrink-0" style="min-width:90px;">
|
||||
<input type="number" id="customPowderOrderPriceInput"
|
||||
class="form-control form-control-sm text-end"
|
||||
min="0" step="0.01" placeholder="0.00"
|
||||
title="Enter total including any shipping"
|
||||
oninput="onCustomPowderPriceEdit(this.value)">
|
||||
</div>
|
||||
<div style="min-width:66px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="hiddenFieldsContainer"></div>
|
||||
<div id="aiPhotoTempIdsContainer"></div>
|
||||
</div>
|
||||
@@ -396,7 +418,7 @@
|
||||
sequence = c.Sequence,
|
||||
inventoryItemId = c.InventoryItemId,
|
||||
colorName = c.ColorName,
|
||||
vendorId = c.VendorId,
|
||||
supplierId = c.VendorId,
|
||||
colorCode = c.ColorCode,
|
||||
finish = c.Finish,
|
||||
coverageSqFtPerLb = c.CoverageSqFtPerLb,
|
||||
|
||||
@@ -50,6 +50,28 @@
|
||||
<p class="small">Click <strong>Add Item</strong> to get started.</p>
|
||||
</div>
|
||||
<div id="itemCardsContainer"></div>
|
||||
<div id="customPowderOrderPreview" class="d-none mt-2">
|
||||
<div class="quote-item-card border-warning" style="border-style:dashed!important;background:var(--bs-warning-bg-subtle,#fff8e1);">
|
||||
<div class="d-flex align-items-start gap-2">
|
||||
<div class="flex-grow-1">
|
||||
<div class="d-flex align-items-center flex-wrap gap-1 mb-1">
|
||||
<span class="badge bg-warning text-dark item-badge"><i class="bi bi-truck me-1"></i>Powder Order</span>
|
||||
<span class="fw-semibold" id="customPowderOrderPreviewDesc">Custom Powder Order</span>
|
||||
</div>
|
||||
<div class="text-muted small"><span id="customPowderOrderPreviewPrice"></span> — edit total to include shipping</div>
|
||||
</div>
|
||||
<div class="text-center flex-shrink-0" style="min-width:45px;">1</div>
|
||||
<div class="flex-shrink-0" style="min-width:90px;">
|
||||
<input type="number" id="customPowderOrderPriceInput"
|
||||
class="form-control form-control-sm text-end"
|
||||
min="0" step="0.01" placeholder="0.00"
|
||||
title="Enter total including any shipping"
|
||||
oninput="onCustomPowderPriceEdit(this.value)">
|
||||
</div>
|
||||
<div style="min-width:66px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Hidden fields written by wizard JS -->
|
||||
<div id="hiddenFieldsContainer"></div>
|
||||
@@ -149,7 +171,7 @@
|
||||
sequence = c.Sequence,
|
||||
inventoryItemId = c.InventoryItemId,
|
||||
colorName = c.ColorName,
|
||||
vendorId = c.VendorId,
|
||||
supplierId = c.VendorId,
|
||||
colorCode = c.ColorCode,
|
||||
finish = c.Finish,
|
||||
coverageSqFtPerLb = c.CoverageSqFtPerLb,
|
||||
|
||||
@@ -268,6 +268,28 @@
|
||||
<p class="small">Click <strong>Add Item</strong> to get started.</p>
|
||||
</div>
|
||||
<div id="itemCardsContainer"></div>
|
||||
<div id="customPowderOrderPreview" class="d-none mt-2">
|
||||
<div class="quote-item-card border-warning" style="border-style:dashed!important;background:var(--bs-warning-bg-subtle,#fff8e1);">
|
||||
<div class="d-flex align-items-start gap-2">
|
||||
<div class="flex-grow-1">
|
||||
<div class="d-flex align-items-center flex-wrap gap-1 mb-1">
|
||||
<span class="badge bg-warning text-dark item-badge"><i class="bi bi-truck me-1"></i>Powder Order</span>
|
||||
<span class="fw-semibold" id="customPowderOrderPreviewDesc">Custom Powder Order</span>
|
||||
</div>
|
||||
<div class="text-muted small"><span id="customPowderOrderPreviewPrice"></span> — edit total to include shipping</div>
|
||||
</div>
|
||||
<div class="text-center flex-shrink-0" style="min-width:45px;">1</div>
|
||||
<div class="flex-shrink-0" style="min-width:90px;">
|
||||
<input type="number" id="customPowderOrderPriceInput"
|
||||
class="form-control form-control-sm text-end"
|
||||
min="0" step="0.01" placeholder="0.00"
|
||||
title="Enter total including any shipping"
|
||||
oninput="onCustomPowderPriceEdit(this.value)">
|
||||
</div>
|
||||
<div style="min-width:66px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Hidden fields written by wizard JS -->
|
||||
<div id="hiddenFieldsContainer"></div>
|
||||
@@ -477,7 +499,7 @@
|
||||
sequence = c.Sequence,
|
||||
inventoryItemId = c.InventoryItemId,
|
||||
colorName = c.ColorName,
|
||||
vendorId = c.VendorId,
|
||||
supplierId = c.VendorId,
|
||||
colorCode = c.ColorCode,
|
||||
finish = c.Finish,
|
||||
coverageSqFtPerLb = c.CoverageSqFtPerLb,
|
||||
|
||||
@@ -251,6 +251,13 @@
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
@if (Model.QuoteItems != null && Model.QuoteItems.Any(i => i.Description != null && i.Description.StartsWith("Custom Powder Order")))
|
||||
{
|
||||
<div class="alert alert-warning alert-permanent mb-3" role="alert">
|
||||
<i class="bi bi-truck me-2"></i><strong>Custom Powder Order</strong> —
|
||||
The line item below shows material cost only. Add shipping charges before sending this quote to the customer.
|
||||
</div>
|
||||
}
|
||||
@if (Model.QuoteItems != null && Model.QuoteItems.Any())
|
||||
{
|
||||
var catalogItems = Model.QuoteItems.Where(i => i.CatalogItemId.HasValue).ToList();
|
||||
|
||||
@@ -231,6 +231,28 @@
|
||||
<p class="small">Click <strong>Add Item</strong> to get started.</p>
|
||||
</div>
|
||||
<div id="itemCardsContainer"></div>
|
||||
<div id="customPowderOrderPreview" class="d-none mt-2">
|
||||
<div class="quote-item-card border-warning" style="border-style:dashed!important;background:var(--bs-warning-bg-subtle,#fff8e1);">
|
||||
<div class="d-flex align-items-start gap-2">
|
||||
<div class="flex-grow-1">
|
||||
<div class="d-flex align-items-center flex-wrap gap-1 mb-1">
|
||||
<span class="badge bg-warning text-dark item-badge"><i class="bi bi-truck me-1"></i>Powder Order</span>
|
||||
<span class="fw-semibold" id="customPowderOrderPreviewDesc">Custom Powder Order</span>
|
||||
</div>
|
||||
<div class="text-muted small"><span id="customPowderOrderPreviewPrice"></span> — edit total to include shipping</div>
|
||||
</div>
|
||||
<div class="text-center flex-shrink-0" style="min-width:45px;">1</div>
|
||||
<div class="flex-shrink-0" style="min-width:90px;">
|
||||
<input type="number" id="customPowderOrderPriceInput"
|
||||
class="form-control form-control-sm text-end"
|
||||
min="0" step="0.01" placeholder="0.00"
|
||||
title="Enter total including any shipping"
|
||||
oninput="onCustomPowderPriceEdit(this.value)">
|
||||
</div>
|
||||
<div style="min-width:66px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Hidden fields written by wizard JS -->
|
||||
<div id="hiddenFieldsContainer"></div>
|
||||
@@ -500,6 +522,7 @@
|
||||
powderCostOverride = item.PowderCostOverride,
|
||||
isGenericItem = item.IsGenericItem,
|
||||
isLaborItem = item.IsLaborItem,
|
||||
isSalesItem = item.IsSalesItem,
|
||||
isAiItem = item.IsAiItem,
|
||||
isCustomFormulaItem = item.IsCustomFormulaItem,
|
||||
customItemTemplateId = item.CustomItemTemplateId,
|
||||
@@ -521,7 +544,7 @@
|
||||
sequence = c.Sequence,
|
||||
inventoryItemId = c.InventoryItemId,
|
||||
colorName = c.ColorName,
|
||||
vendorId = c.VendorId,
|
||||
supplierId = c.VendorId,
|
||||
colorCode = c.ColorCode,
|
||||
finish = c.Finish,
|
||||
coverageSqFtPerLb = c.CoverageSqFtPerLb,
|
||||
|
||||
Reference in New Issue
Block a user