Add Custom Formula Item Templates with AI generation and wizard integration
Introduces per-company reusable NCalc2 pricing formula templates for complex fabricated items (roof curbs, enclosures, welded frames). Templates support two output modes — FixedRate (formula yields a dollar amount) and SurfaceAreaSqFt (formula yields sq ft fed into the standard coating engine). Includes: - CustomItemTemplate entity, migration (AddCustomItemTemplates), IUnitOfWork repo - IsCustomFormulaItem / CustomItemTemplateId / FormulaFieldValuesJson flags on QuoteItem, JobItem, CreateQuoteItemDto; mapped in all 3 JobItemAssemblyService overloads and all existingItemsData JSON projections + pageMeta blocks - ICustomFormulaAiService / CustomFormulaAiService: Claude-powered formula generator (natural language + optional diagram image) and NCalc2 evaluator - CompanySettings CRUD endpoints: GetCustomItemTemplates, Create/Update/Delete, UploadTemplateDiagram, TemplateDiagram (blob serve), EvaluateFormula, GenerateFormulaFromAi - Company Settings "Custom Formulas" tab + cfModal + company-settings-custom-formulas.js - item-wizard.js: formula item type card, renderFormulaFields, wzFormulaRecalc (live evaluate via POST), collectStep2 formula branch, buildCardHtml / emitHiddenFields - Formula badge in Quotes/Details and Jobs/Details; AI badge gap fixed in Jobs/Details - Help article (CustomFormulaTemplates.cshtml), Help Index card, HelpController action, HelpKnowledgeBase entry; 225/225 unit tests passing Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -106,6 +106,11 @@
|
||||
<i class="bi bi-tablet"></i> Kiosk
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="custom-formulas-tab" data-bs-toggle="tab" data-bs-target="#custom-formulas" type="button" role="tab">
|
||||
<i class="bi bi-calculator"></i> Custom Formulas
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- Tabs Content -->
|
||||
@@ -2054,6 +2059,143 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Custom Formula Item Templates ──────────────────────────────── -->
|
||||
<div class="tab-pane fade" id="custom-formulas" role="tabpanel">
|
||||
<div class="card shadow-sm mt-3">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0"><i class="bi bi-calculator me-2"></i>Custom Formula Item Templates</h5>
|
||||
<button type="button" class="btn btn-primary btn-sm" onclick="cfShowCreate()">
|
||||
<i class="bi bi-plus-circle me-1"></i>New Template
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="text-muted small mb-3">
|
||||
Define reusable pricing formulas for complex fabricated items (roof curbs, enclosures, frames).
|
||||
When a user adds a formula item to a quote or job, they fill in the measurements and the formula
|
||||
calculates the price automatically.
|
||||
</p>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-hover align-middle" id="cfTemplatesTable">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Output Mode</th>
|
||||
<th>Fields</th>
|
||||
<th>Active</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="cfTemplatesBody">
|
||||
<tr><td colspan="5" class="text-muted text-center py-3">Loading…</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Custom Formula Template Modal -->
|
||||
<div class="modal fade" id="cfModal" tabindex="-1" aria-labelledby="cfModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-xl modal-dialog-scrollable">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="cfModalLabel">New Formula Template</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<input type="hidden" id="cfId" value="0" />
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Name <span class="text-danger">*</span></label>
|
||||
<input type="text" id="cfName" class="form-control" placeholder="e.g. Roof Curb" maxlength="100" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Description</label>
|
||||
<input type="text" id="cfDescription" class="form-control" maxlength="500" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Output Mode <span class="text-danger">*</span></label>
|
||||
<select id="cfOutputMode" class="form-select" onchange="cfToggleRateFields()">
|
||||
<option value="FixedRate">Fixed Rate — formula → $ amount</option>
|
||||
<option value="SurfaceAreaSqFt">Surface Area — formula → sq ft (standard pricing engine prices it)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div id="cfRateFields">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Default Rate</label>
|
||||
<input type="number" id="cfDefaultRate" class="form-control" step="0.01" placeholder="e.g. 0.85" />
|
||||
<div class="form-text">Used as the <code>rate</code> variable if not overridden per-quote.</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Rate Label</label>
|
||||
<input type="text" id="cfRateLabel" class="form-control" maxlength="50" placeholder="e.g. $/sq ft" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Formula <span class="text-danger">*</span></label>
|
||||
<input type="text" id="cfFormula" class="form-control font-monospace" placeholder="e.g. 2*(L*W + L*H + W*H)/144 * rate" />
|
||||
<div class="form-text">NCalc expression. Available variables: field names + <code>rate</code>.</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Notes</label>
|
||||
<textarea id="cfNotes" class="form-control" rows="2" maxlength="1000"></textarea>
|
||||
</div>
|
||||
<div class="form-check mb-3">
|
||||
<input type="checkbox" id="cfIsActive" class="form-check-input" checked />
|
||||
<label class="form-check-label" for="cfIsActive">Active (show in quote/job wizard)</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Fields</label>
|
||||
<div class="form-text mb-2">Define the measurement inputs users will fill in.</div>
|
||||
<div id="cfFieldsList"></div>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm mt-2" onclick="cfAddField()">
|
||||
<i class="bi bi-plus"></i> Add Field
|
||||
</button>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Formula Test</label>
|
||||
<div class="d-flex gap-2 align-items-center">
|
||||
<button type="button" class="btn btn-outline-primary btn-sm" onclick="cfTestFormula()">
|
||||
<i class="bi bi-play-circle"></i> Run
|
||||
</button>
|
||||
<span id="cfTestResult" class="fw-bold"></span>
|
||||
</div>
|
||||
<div class="form-text">Uses the default values from your field list.</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Diagram / Shop Drawing</label>
|
||||
<div id="cfDiagramPreview" class="mb-2" style="display:none;">
|
||||
<img id="cfDiagramImg" src="" alt="Diagram" class="img-fluid rounded border" style="max-height:180px;" />
|
||||
</div>
|
||||
<input type="file" id="cfDiagramFile" class="form-control form-control-sm" accept="image/*" onchange="cfPreviewDiagram(event)" />
|
||||
<div class="form-text">Optional. Upload a shop drawing or photo to help users recognize this item.</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">AI Formula Generator</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="cfAiPrompt" class="form-control" placeholder="Describe the item, e.g. 'Rectangular roof curb with flanged base'" />
|
||||
<button type="button" class="btn btn-outline-secondary" onclick="cfGenerateFromAi()" id="cfAiBtn">
|
||||
<i class="bi bi-stars"></i> Generate
|
||||
</button>
|
||||
</div>
|
||||
<div class="form-text">Claude will suggest a formula, fields, and mode. You can edit before saving.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="button" class="btn btn-primary" onclick="cfSave()">
|
||||
<i class="bi bi-floppy me-1"></i>Save Template
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3289,6 +3431,17 @@
|
||||
const btn = document.querySelector('[data-bs-target="#kiosk"]');
|
||||
if (btn) new bootstrap.Tab(btn).show();
|
||||
}
|
||||
if (urlParams.get('tab') === 'custom-formulas') {
|
||||
const btn = document.querySelector('[data-bs-target="#custom-formulas"]');
|
||||
if (btn) new bootstrap.Tab(btn).show();
|
||||
}
|
||||
</script>
|
||||
<script src="~/js/company-settings-custom-formulas.js" asp-append-version="true"></script>
|
||||
<script>
|
||||
// Load formula templates when the tab is first shown
|
||||
document.querySelector('[data-bs-target="#custom-formulas"]').addEventListener('shown.bs.tab', () => {
|
||||
if (!window._cfLoaded) { cfLoadTemplates(); window._cfLoaded = true; }
|
||||
});
|
||||
</script>
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
@{
|
||||
ViewData["Title"] = "Custom Formula Item Templates — Help";
|
||||
}
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="col-lg-9">
|
||||
<nav aria-label="breadcrumb" class="mb-3">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a asp-controller="Help" asp-action="Index">Help Center</a></li>
|
||||
<li class="breadcrumb-item active">Custom Formula Item Templates</li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<h1 class="h3 mb-1"><i class="bi bi-calculator text-info me-2"></i>Custom Formula Item Templates</h1>
|
||||
<p class="text-muted mb-4">Build reusable pricing formulas for complex fabricated items.</p>
|
||||
|
||||
<div class="card border-0 shadow-sm mb-4">
|
||||
<div class="card-body">
|
||||
<h2 class="h5">What are formula templates?</h2>
|
||||
<p>
|
||||
Some items — roof curbs, electrical enclosures, welded frames — have prices that depend on
|
||||
exact measurements rather than estimated surface area. Custom Formula Item Templates let you define a
|
||||
reusable NCalc expression that automatically calculates the price (or surface area) once a user enters
|
||||
the measurements.
|
||||
</p>
|
||||
<p>
|
||||
Templates are created once in <strong>Company Settings → Custom Formulas</strong> and then
|
||||
appear as a selectable item type in the quote and job item wizards.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card border-0 shadow-sm mb-4">
|
||||
<div class="card-body">
|
||||
<h2 class="h5">Output modes</h2>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm">
|
||||
<thead class="table-light">
|
||||
<tr><th>Mode</th><th>Formula output</th><th>How it’s priced</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>Fixed Rate</strong></td>
|
||||
<td>A dollar amount</td>
|
||||
<td>Stored as <code>ManualUnitPrice</code>; multiplied by quantity for the line total.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Surface Area</strong></td>
|
||||
<td>Square footage</td>
|
||||
<td>Passed to the standard coating engine; priced per your operating cost rates.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card border-0 shadow-sm mb-4">
|
||||
<div class="card-body">
|
||||
<h2 class="h5">Creating a template</h2>
|
||||
<ol>
|
||||
<li>Go to <strong>Company Settings → Custom Formulas</strong> and click <strong>New Template</strong>.</li>
|
||||
<li>Enter a name and choose the output mode.</li>
|
||||
<li>Add the measurement <strong>fields</strong> users will fill in (e.g. <code>length_in</code>, <code>width_in</code>).</li>
|
||||
<li>Write the <strong>formula</strong> using those field names. Example for a box surface area in inches:
|
||||
<pre class="bg-light p-2 rounded mt-1 mb-0">2*(length_in*width_in + length_in*height_in + width_in*height_in) / 144 * rate</pre>
|
||||
</li>
|
||||
<li>Click <strong>Run</strong> to test the formula with your default values.</li>
|
||||
<li>Optionally upload a <strong>diagram image</strong> — users will see it when they select this template.</li>
|
||||
<li>Save the template.</li>
|
||||
</ol>
|
||||
|
||||
<h3 class="h6 mt-3">Using AI to generate a formula</h3>
|
||||
<p>
|
||||
In the template editor, enter a description of the item in the <strong>AI Formula Generator</strong> box
|
||||
and optionally attach a diagram image. Claude will suggest a formula, field list, and output mode.
|
||||
Review and adjust before saving.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card border-0 shadow-sm mb-4">
|
||||
<div class="card-body">
|
||||
<h2 class="h5">Adding a formula item to a quote or job</h2>
|
||||
<ol>
|
||||
<li>In the item wizard, select <strong>Custom Formula Item</strong> (only visible if at least one active template exists).</li>
|
||||
<li>Choose a template from the dropdown. The template’s diagram will appear for reference.</li>
|
||||
<li>Enter the measurements and click <strong>Calculate</strong> to preview the result.</li>
|
||||
<li>Adjust the description and quantity, then continue to the coatings and prep steps.</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card border-0 shadow-sm mb-4">
|
||||
<div class="card-body">
|
||||
<h2 class="h5">NCalc formula reference</h2>
|
||||
<p>Formulas use <a href="https://ncalc.github.io/ncalc/" target="_blank" rel="noopener">NCalc</a> syntax:</p>
|
||||
<ul>
|
||||
<li>Standard operators: <code>+ - * / % Pow(b, e)</code></li>
|
||||
<li>Functions: <code>Abs(x) Round(x, d) Max(a, b) Min(a, b) Sqrt(x)</code></li>
|
||||
<li>Variable names must start with a letter and contain only letters, digits, or underscores.</li>
|
||||
<li>The reserved variable <code>rate</code> is pre-populated from the template’s Default Rate.</li>
|
||||
</ul>
|
||||
<p class="mb-0">Examples:</p>
|
||||
<ul class="mb-0">
|
||||
<li><code>2*(l*w + l*h + w*h) / 144 * rate</code> — box surface area (inches → sqft → dollars)</li>
|
||||
<li><code>Pow(diameter_in / 2, 2) * 3.14159 / 144 * rate</code> — circular face area</li>
|
||||
<li><code>(l_ft * w_ft) * rate</code> — flat panel in feet</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-3 d-none d-lg-block">
|
||||
@await Html.PartialAsync("_HelpNav")
|
||||
</div>
|
||||
</div>
|
||||
@@ -258,6 +258,22 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="card border-0 shadow-sm h-100">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-start gap-3">
|
||||
<div class="rounded-3 bg-info bg-opacity-10 p-2 flex-shrink-0">
|
||||
<i class="bi bi-calculator text-info fs-4"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h5 class="card-title mb-1">Custom Formula Item Templates</h5>
|
||||
<p class="card-text text-muted small mb-2">Build reusable NCalc pricing formulas for complex fabricated items like roof curbs, enclosures, and frames.</p>
|
||||
<a asp-controller="Help" asp-action="CustomFormulaTemplates" class="btn btn-sm btn-outline-info">Read more <i class="bi bi-arrow-right ms-1"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -397,6 +397,11 @@
|
||||
complexity = item.Complexity,
|
||||
isGenericItem = item.IsGenericItem,
|
||||
isLaborItem = item.IsLaborItem,
|
||||
isSalesItem = item.IsSalesItem,
|
||||
isAiItem = item.IsAiItem,
|
||||
isCustomFormulaItem = item.IsCustomFormulaItem,
|
||||
customItemTemplateId = item.CustomItemTemplateId,
|
||||
formulaFieldValuesJson = item.FormulaFieldValuesJson,
|
||||
requiresSandblasting = item.RequiresSandblasting,
|
||||
requiresMasking = item.RequiresMasking,
|
||||
notes = item.Notes,
|
||||
@@ -438,6 +443,8 @@
|
||||
"aiUploadUrl": "@Url.Action("UploadAiPhoto", "Quotes")",
|
||||
"aiAnalyzeUrl": "@Url.Action("AiAnalyzeItem", "Quotes")",
|
||||
"aiPhotoQuotesEnabled": @Json.Serialize((bool)(ViewBag.AiPhotoQuotesEnabled ?? true)),
|
||||
"customFormulaTemplates": @Json.Serialize(ViewBag.CustomFormulaTemplates ?? new List<object>()),
|
||||
"formulaEvalUrl": "@Url.Action("EvaluateFormula", "CompanySettings")",
|
||||
"itemsFieldPrefix": "JobItems",
|
||||
"aiRecalcUrl": "@Url.Action("AiRecalcPrice", "Quotes")"
|
||||
}
|
||||
|
||||
@@ -358,6 +358,8 @@
|
||||
<tr data-item-id="@item.Id">
|
||||
<td>
|
||||
<span data-inline-field="description" data-raw-value="@item.Description"><strong>@item.Description</strong></span>
|
||||
@if (item.IsAiItem) { <span class="badge bg-purple ms-1">AI</span> }
|
||||
@if (item.IsCustomFormulaItem) { <span class="badge bg-secondary ms-1"><i class="bi bi-calculator me-1"></i>Formula</span> }
|
||||
@if (item.Coats != null && item.Coats.Any())
|
||||
{
|
||||
<br />
|
||||
|
||||
@@ -384,6 +384,9 @@
|
||||
isGenericItem = item.IsGenericItem,
|
||||
isLaborItem = item.IsLaborItem,
|
||||
isAiItem = item.IsAiItem,
|
||||
isCustomFormulaItem = item.IsCustomFormulaItem,
|
||||
customItemTemplateId = item.CustomItemTemplateId,
|
||||
formulaFieldValuesJson = item.FormulaFieldValuesJson,
|
||||
requiresSandblasting = item.RequiresSandblasting,
|
||||
requiresMasking = item.RequiresMasking,
|
||||
notes = item.Notes,
|
||||
@@ -425,6 +428,8 @@
|
||||
"aiUploadUrl": "@Url.Action("UploadAiPhoto", "Quotes")",
|
||||
"aiAnalyzeUrl": "@Url.Action("AiAnalyzeItem", "Quotes")",
|
||||
"aiPhotoQuotesEnabled": @Json.Serialize((bool)(ViewBag.AiPhotoQuotesEnabled ?? true)),
|
||||
"customFormulaTemplates": @Json.Serialize(ViewBag.CustomFormulaTemplates ?? new List<object>()),
|
||||
"formulaEvalUrl": "@Url.Action("EvaluateFormula", "CompanySettings")",
|
||||
"itemsFieldPrefix": "JobItems",
|
||||
"aiRecalcUrl": "@Url.Action("AiRecalcPrice", "Quotes")"
|
||||
}
|
||||
|
||||
@@ -137,6 +137,9 @@
|
||||
isGenericItem = item.IsGenericItem,
|
||||
isLaborItem = item.IsLaborItem,
|
||||
isAiItem = item.IsAiItem,
|
||||
isCustomFormulaItem = item.IsCustomFormulaItem,
|
||||
customItemTemplateId = item.CustomItemTemplateId,
|
||||
formulaFieldValuesJson = item.FormulaFieldValuesJson,
|
||||
requiresSandblasting = item.RequiresSandblasting,
|
||||
requiresMasking = item.RequiresMasking,
|
||||
notes = item.Notes,
|
||||
|
||||
@@ -464,6 +464,11 @@
|
||||
manualUnitPrice = item.ManualUnitPrice,
|
||||
isGenericItem = item.IsGenericItem,
|
||||
isLaborItem = item.IsLaborItem,
|
||||
isSalesItem = item.IsSalesItem,
|
||||
isAiItem = item.IsAiItem,
|
||||
isCustomFormulaItem = item.IsCustomFormulaItem,
|
||||
customItemTemplateId = item.CustomItemTemplateId,
|
||||
formulaFieldValuesJson = item.FormulaFieldValuesJson,
|
||||
requiresSandblasting = item.RequiresSandblasting,
|
||||
requiresMasking = item.RequiresMasking,
|
||||
notes = item.Notes,
|
||||
@@ -505,6 +510,8 @@
|
||||
"aiUploadUrl": "@Url.Action("UploadAiPhoto", "Quotes")",
|
||||
"aiAnalyzeUrl": "@Url.Action("AiAnalyzeItem", "Quotes")",
|
||||
"aiPhotoQuotesEnabled": @Json.Serialize((bool)(ViewBag.AiPhotoQuotesEnabled ?? true)),
|
||||
"customFormulaTemplates": @Json.Serialize(ViewBag.CustomFormulaTemplates ?? new List<object>()),
|
||||
"formulaEvalUrl": "@Url.Action("EvaluateFormula", "CompanySettings")",
|
||||
"itemsFieldPrefix": "QuoteItems",
|
||||
"aiRecalcUrl": "@Url.Action("AiRecalcPrice", "Quotes")"
|
||||
}
|
||||
|
||||
@@ -1300,6 +1300,7 @@
|
||||
<span class="fw-semibold">@(item.Description ?? item.CatalogItemName ?? "(no description)")</span>
|
||||
@if (item.CatalogItemId.HasValue) { <span class="badge bg-primary ms-1">Catalog</span> }
|
||||
@if (item.IsAiItem) { <span class="badge bg-purple ms-1">AI</span> }
|
||||
@if (item.IsCustomFormulaItem) { <span class="badge bg-secondary ms-1"><i class="bi bi-calculator me-1"></i>Formula</span> }
|
||||
@if (item.SurfaceAreaSqFt > 0 || item.EstimatedMinutes > 0)
|
||||
{
|
||||
<span class="text-muted ms-2" style="font-size:.8rem;">
|
||||
|
||||
@@ -501,6 +501,9 @@
|
||||
isGenericItem = item.IsGenericItem,
|
||||
isLaborItem = item.IsLaborItem,
|
||||
isAiItem = item.IsAiItem,
|
||||
isCustomFormulaItem = item.IsCustomFormulaItem,
|
||||
customItemTemplateId = item.CustomItemTemplateId,
|
||||
formulaFieldValuesJson = item.FormulaFieldValuesJson,
|
||||
includePrepCost = item.IncludePrepCost,
|
||||
complexity = item.Complexity,
|
||||
aiTags = item.AiTags,
|
||||
@@ -548,6 +551,8 @@
|
||||
"aiUploadUrl": "@Url.Action("UploadAiPhoto", "Quotes")",
|
||||
"aiAnalyzeUrl": "@Url.Action("AiAnalyzeItem", "Quotes")",
|
||||
"aiPhotoQuotesEnabled": @Json.Serialize((bool)(ViewBag.AiPhotoQuotesEnabled ?? true)),
|
||||
"customFormulaTemplates": @Json.Serialize(ViewBag.CustomFormulaTemplates ?? new List<object>()),
|
||||
"formulaEvalUrl": "@Url.Action("EvaluateFormula", "CompanySettings")",
|
||||
"itemsFieldPrefix": "QuoteItems",
|
||||
"aiRecalcUrl": "@Url.Action("AiRecalcPrice", "Quotes")",
|
||||
"emailOptOutCustomerIds": @Html.Raw(System.Text.Json.JsonSerializer.Serialize(ViewBag.CustomerEmailOptOutIds ?? new System.Collections.Generic.HashSet<int>())),
|
||||
|
||||
Reference in New Issue
Block a user