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:
@@ -2429,6 +2429,25 @@ public class QuotesController : Controller
|
||||
var (_, quotePhotoMax) = await _subscriptionService.GetQuotePhotoCountAsync(companyId, 0);
|
||||
ViewBag.QuotePhotosEnabled = quotePhotoMax != 0; // 0 = feature disabled for this plan
|
||||
|
||||
var formulaTemplates = await _unitOfWork.CustomItemTemplates.FindAsync(
|
||||
t => t.CompanyId == companyId && t.IsActive);
|
||||
ViewBag.CustomFormulaTemplates = formulaTemplates
|
||||
.OrderBy(t => t.DisplayOrder).ThenBy(t => t.Name)
|
||||
.Select(t => new
|
||||
{
|
||||
id = t.Id,
|
||||
name = t.Name,
|
||||
description = t.Description,
|
||||
outputMode = t.OutputMode,
|
||||
fieldsJson = t.FieldsJson,
|
||||
formula = t.Formula,
|
||||
defaultRate = t.DefaultRate,
|
||||
rateLabel = t.RateLabel,
|
||||
diagramImagePath = string.IsNullOrEmpty(t.DiagramImagePath)
|
||||
? (string?)null
|
||||
: Url.Action("TemplateDiagram", "CompanySettings", new { templateId = t.Id })
|
||||
}).ToList();
|
||||
|
||||
// Customers
|
||||
var customers = await _unitOfWork.Customers.FindAsync(c => c.CompanyId == companyId);
|
||||
ViewBag.Customers = customers
|
||||
|
||||
Reference in New Issue
Block a user