Store powder specific gravity and fix coverage math

This commit is contained in:
2026-05-06 08:46:41 -04:00
parent 11a1b91be1
commit f383339465
18 changed files with 9690 additions and 16 deletions
@@ -17,6 +17,7 @@ public class InventoryItemDto
public string? Manufacturer { get; set; } public string? Manufacturer { get; set; }
public string? ManufacturerPartNumber { get; set; } public string? ManufacturerPartNumber { get; set; }
public decimal? CoverageSqFtPerLb { get; set; } public decimal? CoverageSqFtPerLb { get; set; }
public decimal? SpecificGravity { get; set; }
public decimal? TransferEfficiency { get; set; } public decimal? TransferEfficiency { get; set; }
public int? CureTemperatureF { get; set; } public int? CureTemperatureF { get; set; }
public int? CureTimeMinutes { get; set; } public int? CureTimeMinutes { get; set; }
@@ -125,6 +126,10 @@ public class CreateInventoryItemDto
[Display(Name = "Coverage (Sq Ft/Lb)")] [Display(Name = "Coverage (Sq Ft/Lb)")]
public decimal? CoverageSqFtPerLb { get; set; } public decimal? CoverageSqFtPerLb { get; set; }
[Range(0, 100, ErrorMessage = "Specific gravity must be between 0 and 100")]
[Display(Name = "Specific Gravity")]
public decimal? SpecificGravity { get; set; }
[Range(0, 100, ErrorMessage = "Transfer efficiency must be between 0 and 100%")] [Range(0, 100, ErrorMessage = "Transfer efficiency must be between 0 and 100%")]
[Display(Name = "Transfer Efficiency (%)")] [Display(Name = "Transfer Efficiency (%)")]
public decimal? TransferEfficiency { get; set; } public decimal? TransferEfficiency { get; set; }
@@ -23,6 +23,7 @@ public class PowderCatalogLookupResult
public string? ColorFamilies { get; set; } public string? ColorFamilies { get; set; }
public bool? RequiresClearCoat { get; set; } public bool? RequiresClearCoat { get; set; }
public decimal? CoverageSqFtPerLb { get; set; } public decimal? CoverageSqFtPerLb { get; set; }
public decimal? SpecificGravity { get; set; }
public decimal? TransferEfficiency { get; set; } public decimal? TransferEfficiency { get; set; }
} }
@@ -20,6 +20,7 @@ public class InventoryItem : BaseEntity
public string? Manufacturer { get; set; } public string? Manufacturer { get; set; }
public string? ManufacturerPartNumber { get; set; } public string? ManufacturerPartNumber { get; set; }
public decimal? CoverageSqFtPerLb { get; set; } // Square feet coverage per pound (default 30) public decimal? CoverageSqFtPerLb { get; set; } // Square feet coverage per pound (default 30)
public decimal? SpecificGravity { get; set; } // Powder specific gravity from the technical data sheet
public decimal? TransferEfficiency { get; set; } // Percentage of powder that sticks (default 65%) public decimal? TransferEfficiency { get; set; } // Percentage of powder that sticks (default 65%)
public decimal? CureTemperatureF { get; set; } // Required cure temperature in °F (recommended for oven scheduling) public decimal? CureTemperatureF { get; set; } // Required cure temperature in °F (recommended for oven scheduling)
public int? CureTimeMinutes { get; set; } // Required hold time at cure temperature public int? CureTimeMinutes { get; set; } // Required hold time at cure temperature
@@ -52,6 +52,9 @@ public class PowderCatalogItem
/// <summary>Theoretical coverage in sq ft per pound. Typical 80120.</summary> /// <summary>Theoretical coverage in sq ft per pound. Typical 80120.</summary>
public decimal? CoverageSqFtPerLb { get; set; } public decimal? CoverageSqFtPerLb { get; set; }
/// <summary>Specific gravity from the TDS. Used to derive theoretical coverage when needed.</summary>
public decimal? SpecificGravity { get; set; }
/// <summary>Powder transfer efficiency percentage. Typical 6075%.</summary> /// <summary>Powder transfer efficiency percentage. Typical 6075%.</summary>
public decimal? TransferEfficiency { get; set; } public decimal? TransferEfficiency { get; set; }
@@ -0,0 +1,81 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace PowderCoating.Infrastructure.Migrations
{
/// <inheritdoc />
public partial class AddSpecificGravityToPowderCatalogAndInventory : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<decimal>(
name: "SpecificGravity",
table: "PowderCatalogItems",
type: "decimal(18,2)",
nullable: true);
migrationBuilder.AddColumn<decimal>(
name: "SpecificGravity",
table: "InventoryItems",
type: "decimal(18,2)",
nullable: true);
migrationBuilder.UpdateData(
table: "PricingTiers",
keyColumn: "Id",
keyValue: 1,
column: "CreatedAt",
value: new DateTime(2026, 5, 6, 12, 35, 37, 694, DateTimeKind.Utc).AddTicks(5288));
migrationBuilder.UpdateData(
table: "PricingTiers",
keyColumn: "Id",
keyValue: 2,
column: "CreatedAt",
value: new DateTime(2026, 5, 6, 12, 35, 37, 694, DateTimeKind.Utc).AddTicks(5294));
migrationBuilder.UpdateData(
table: "PricingTiers",
keyColumn: "Id",
keyValue: 3,
column: "CreatedAt",
value: new DateTime(2026, 5, 6, 12, 35, 37, 694, DateTimeKind.Utc).AddTicks(5296));
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "SpecificGravity",
table: "PowderCatalogItems");
migrationBuilder.DropColumn(
name: "SpecificGravity",
table: "InventoryItems");
migrationBuilder.UpdateData(
table: "PricingTiers",
keyColumn: "Id",
keyValue: 1,
column: "CreatedAt",
value: new DateTime(2026, 5, 6, 2, 7, 22, 625, DateTimeKind.Utc).AddTicks(2199));
migrationBuilder.UpdateData(
table: "PricingTiers",
keyColumn: "Id",
keyValue: 2,
column: "CreatedAt",
value: new DateTime(2026, 5, 6, 2, 7, 22, 625, DateTimeKind.Utc).AddTicks(2206));
migrationBuilder.UpdateData(
table: "PricingTiers",
keyColumn: "Id",
keyValue: 3,
column: "CreatedAt",
value: new DateTime(2026, 5, 6, 2, 7, 22, 625, DateTimeKind.Utc).AddTicks(2208));
}
}
}
@@ -3332,6 +3332,9 @@ namespace PowderCoating.Infrastructure.Migrations
b.Property<string>("SpecPageUrl") b.Property<string>("SpecPageUrl")
.HasColumnType("nvarchar(max)"); .HasColumnType("nvarchar(max)");
b.Property<decimal?>("SpecificGravity")
.HasColumnType("decimal(18,2)");
b.Property<string>("TdsUrl") b.Property<string>("TdsUrl")
.HasColumnType("nvarchar(max)"); .HasColumnType("nvarchar(max)");
@@ -5835,6 +5838,9 @@ namespace PowderCoating.Infrastructure.Migrations
.IsRequired() .IsRequired()
.HasColumnType("nvarchar(450)"); .HasColumnType("nvarchar(450)");
b.Property<decimal?>("SpecificGravity")
.HasColumnType("decimal(18,2)");
b.Property<string>("TdsUrl") b.Property<string>("TdsUrl")
.HasColumnType("nvarchar(max)"); .HasColumnType("nvarchar(max)");
@@ -6053,7 +6059,7 @@ namespace PowderCoating.Infrastructure.Migrations
{ {
Id = 1, Id = 1,
CompanyId = 0, CompanyId = 0,
CreatedAt = new DateTime(2026, 5, 6, 2, 7, 22, 625, DateTimeKind.Utc).AddTicks(2199), CreatedAt = new DateTime(2026, 5, 6, 12, 35, 37, 694, DateTimeKind.Utc).AddTicks(5288),
Description = "Standard pricing for regular customers", Description = "Standard pricing for regular customers",
DiscountPercent = 0m, DiscountPercent = 0m,
IsActive = true, IsActive = true,
@@ -6064,7 +6070,7 @@ namespace PowderCoating.Infrastructure.Migrations
{ {
Id = 2, Id = 2,
CompanyId = 0, CompanyId = 0,
CreatedAt = new DateTime(2026, 5, 6, 2, 7, 22, 625, DateTimeKind.Utc).AddTicks(2206), CreatedAt = new DateTime(2026, 5, 6, 12, 35, 37, 694, DateTimeKind.Utc).AddTicks(5294),
Description = "5% discount for preferred customers", Description = "5% discount for preferred customers",
DiscountPercent = 5m, DiscountPercent = 5m,
IsActive = true, IsActive = true,
@@ -6075,7 +6081,7 @@ namespace PowderCoating.Infrastructure.Migrations
{ {
Id = 3, Id = 3,
CompanyId = 0, CompanyId = 0,
CreatedAt = new DateTime(2026, 5, 6, 2, 7, 22, 625, DateTimeKind.Utc).AddTicks(2208), CreatedAt = new DateTime(2026, 5, 6, 12, 35, 37, 694, DateTimeKind.Utc).AddTicks(5296),
Description = "10% discount for premium customers", Description = "10% discount for premium customers",
DiscountPercent = 10m, DiscountPercent = 10m,
IsActive = true, IsActive = true,
@@ -28,7 +28,8 @@ namespace PowderCoating.Infrastructure.Services;
public class InventoryAiLookupService : IInventoryAiLookupService public class InventoryAiLookupService : IInventoryAiLookupService
{ {
private const decimal DefaultTransferEfficiency = 65m; private const decimal DefaultTransferEfficiency = 65m;
private const decimal TheoreticalCoverageAtOneMilFactor = 192.3m; private const decimal TheoreticalCoverageConstant = 192.3m;
private const decimal DefaultCoverageThicknessMils = 1.5m;
private readonly IConfiguration _config; private readonly IConfiguration _config;
private readonly IHttpClientFactory _httpClientFactory; private readonly IHttpClientFactory _httpClientFactory;
@@ -1247,7 +1248,7 @@ Rules:
if (!result.CoverageSqFtPerLb.HasValue && result.SpecificGravity is > 0) if (!result.CoverageSqFtPerLb.HasValue && result.SpecificGravity is > 0)
{ {
var calculatedCoverage = TheoreticalCoverageAtOneMilFactor / result.SpecificGravity.Value; var calculatedCoverage = TheoreticalCoverageConstant / (result.SpecificGravity.Value * DefaultCoverageThicknessMils);
result.CoverageSqFtPerLb = Math.Round(calculatedCoverage, 2, MidpointRounding.AwayFromZero); result.CoverageSqFtPerLb = Math.Round(calculatedCoverage, 2, MidpointRounding.AwayFromZero);
} }
} }
@@ -747,6 +747,7 @@ public class InventoryController : Controller
if (match.ColorFamilies != null) result.ColorFamilies = match.ColorFamilies; if (match.ColorFamilies != null) result.ColorFamilies = match.ColorFamilies;
if (match.RequiresClearCoat != null) result.RequiresClearCoat = match.RequiresClearCoat; if (match.RequiresClearCoat != null) result.RequiresClearCoat = match.RequiresClearCoat;
if (match.CoverageSqFtPerLb != null) result.CoverageSqFtPerLb = match.CoverageSqFtPerLb; if (match.CoverageSqFtPerLb != null) result.CoverageSqFtPerLb = match.CoverageSqFtPerLb;
if (match.SpecificGravity != null) result.SpecificGravity = match.SpecificGravity;
result.TransferEfficiency ??= GetEffectiveTransferEfficiency(match.TransferEfficiency); result.TransferEfficiency ??= GetEffectiveTransferEfficiency(match.TransferEfficiency);
// URL / price fields: fill gaps only — AI may have found something better // URL / price fields: fill gaps only — AI may have found something better
result.ImageUrl ??= match.ImageUrl; result.ImageUrl ??= match.ImageUrl;
@@ -777,6 +778,7 @@ public class InventoryController : Controller
ColorFamilies = result.ColorFamilies, ColorFamilies = result.ColorFamilies,
RequiresClearCoat = result.RequiresClearCoat, RequiresClearCoat = result.RequiresClearCoat,
CoverageSqFtPerLb = result.CoverageSqFtPerLb, CoverageSqFtPerLb = result.CoverageSqFtPerLb,
SpecificGravity = result.SpecificGravity,
TransferEfficiency = GetEffectiveTransferEfficiency(result.TransferEfficiency), TransferEfficiency = GetEffectiveTransferEfficiency(result.TransferEfficiency),
ImageUrl = result.ImageUrl, ImageUrl = result.ImageUrl,
ProductUrl = result.SpecPageUrl, ProductUrl = result.SpecPageUrl,
@@ -875,6 +877,7 @@ public class InventoryController : Controller
aiResult.CureTimeMinutes ??= full.CureTimeMinutes; aiResult.CureTimeMinutes ??= full.CureTimeMinutes;
aiResult.RequiresClearCoat ??= full.RequiresClearCoat; aiResult.RequiresClearCoat ??= full.RequiresClearCoat;
aiResult.CoverageSqFtPerLb ??= full.CoverageSqFtPerLb; aiResult.CoverageSqFtPerLb ??= full.CoverageSqFtPerLb;
aiResult.SpecificGravity ??= full.SpecificGravity;
aiResult.TransferEfficiency ??= GetEffectiveTransferEfficiency(full.TransferEfficiency); aiResult.TransferEfficiency ??= GetEffectiveTransferEfficiency(full.TransferEfficiency);
aiResult.ManufacturerPartNumber ??= full.ManufacturerPartNumber; aiResult.ManufacturerPartNumber ??= full.ManufacturerPartNumber;
aiResult.ColorName ??= full.ColorName; aiResult.ColorName ??= full.ColorName;
@@ -954,6 +957,7 @@ public class InventoryController : Controller
colorFamilies = aiResult.ColorFamilies, colorFamilies = aiResult.ColorFamilies,
requiresClearCoat = aiResult.RequiresClearCoat, requiresClearCoat = aiResult.RequiresClearCoat,
coverageSqFtPerLb = aiResult.CoverageSqFtPerLb, coverageSqFtPerLb = aiResult.CoverageSqFtPerLb,
specificGravity = aiResult.SpecificGravity,
transferEfficiency = aiResult.TransferEfficiency ?? DefaultTransferEfficiency, transferEfficiency = aiResult.TransferEfficiency ?? DefaultTransferEfficiency,
unitPrice = aiResult.UnitCostPerLb ?? 0m, unitPrice = aiResult.UnitCostPerLb ?? 0m,
imageUrl = aiResult.ImageUrl, imageUrl = aiResult.ImageUrl,
@@ -1106,6 +1110,7 @@ public class InventoryController : Controller
colorFamilies = p.ColorFamilies, colorFamilies = p.ColorFamilies,
requiresClearCoat = p.RequiresClearCoat, requiresClearCoat = p.RequiresClearCoat,
coverageSqFtPerLb = p.CoverageSqFtPerLb, coverageSqFtPerLb = p.CoverageSqFtPerLb,
specificGravity = p.SpecificGravity,
transferEfficiency = GetEffectiveTransferEfficiency(p.TransferEfficiency) transferEfficiency = GetEffectiveTransferEfficiency(p.TransferEfficiency)
}) })
.ToList(); .ToList();
@@ -181,6 +181,7 @@ public class PowderCatalogController : Controller
ColorFamilies = NullIfWhiteSpace(model.ColorFamilies), ColorFamilies = NullIfWhiteSpace(model.ColorFamilies),
RequiresClearCoat = model.RequiresClearCoat, RequiresClearCoat = model.RequiresClearCoat,
CoverageSqFtPerLb = model.CoverageSqFtPerLb, CoverageSqFtPerLb = model.CoverageSqFtPerLb,
SpecificGravity = model.SpecificGravity,
TransferEfficiency = model.TransferEfficiency, TransferEfficiency = model.TransferEfficiency,
IsDiscontinued = model.IsDiscontinued, IsDiscontinued = model.IsDiscontinued,
IsUserContributed = model.IsUserContributed, IsUserContributed = model.IsUserContributed,
@@ -250,6 +251,7 @@ public class PowderCatalogController : Controller
entity.ColorFamilies = NullIfWhiteSpace(model.ColorFamilies); entity.ColorFamilies = NullIfWhiteSpace(model.ColorFamilies);
entity.RequiresClearCoat = model.RequiresClearCoat; entity.RequiresClearCoat = model.RequiresClearCoat;
entity.CoverageSqFtPerLb = model.CoverageSqFtPerLb; entity.CoverageSqFtPerLb = model.CoverageSqFtPerLb;
entity.SpecificGravity = model.SpecificGravity;
entity.TransferEfficiency = model.TransferEfficiency; entity.TransferEfficiency = model.TransferEfficiency;
entity.IsDiscontinued = model.IsDiscontinued; entity.IsDiscontinued = model.IsDiscontinued;
entity.IsUserContributed = model.IsUserContributed; entity.IsUserContributed = model.IsUserContributed;
@@ -412,6 +414,7 @@ public class PowderCatalogController : Controller
TdsUrl = p.TdsUrl, TdsUrl = p.TdsUrl,
ApplicationGuideUrl = p.ApplicationGuideUrl, ApplicationGuideUrl = p.ApplicationGuideUrl,
ProductUrl = p.ProductUrl, ProductUrl = p.ProductUrl,
SpecificGravity = p.SpecificGravity,
IsDiscontinued = p.IsDiscontinued IsDiscontinued = p.IsDiscontinued
}) })
.ToList(); .ToList();
@@ -661,6 +664,7 @@ public class PowderCatalogController : Controller
ColorFamilies = item.ColorFamilies, ColorFamilies = item.ColorFamilies,
RequiresClearCoat = item.RequiresClearCoat, RequiresClearCoat = item.RequiresClearCoat,
CoverageSqFtPerLb = item.CoverageSqFtPerLb, CoverageSqFtPerLb = item.CoverageSqFtPerLb,
SpecificGravity = item.SpecificGravity,
TransferEfficiency = GetEffectiveTransferEfficiency(item.TransferEfficiency), TransferEfficiency = GetEffectiveTransferEfficiency(item.TransferEfficiency),
IsDiscontinued = item.IsDiscontinued, IsDiscontinued = item.IsDiscontinued,
IsUserContributed = item.IsUserContributed, IsUserContributed = item.IsUserContributed,
@@ -69,6 +69,10 @@ public class PowderCatalogFormViewModel
[Display(Name = "Coverage (Sq Ft / Lb)")] [Display(Name = "Coverage (Sq Ft / Lb)")]
public decimal? CoverageSqFtPerLb { get; set; } public decimal? CoverageSqFtPerLb { get; set; }
[Range(0, 100)]
[Display(Name = "Specific Gravity")]
public decimal? SpecificGravity { get; set; }
[Range(0, 100)] [Range(0, 100)]
[Display(Name = "Transfer Efficiency (%)")] [Display(Name = "Transfer Efficiency (%)")]
public decimal? TransferEfficiency { get; set; } public decimal? TransferEfficiency { get; set; }
@@ -159,21 +159,35 @@
</button> </button>
</div> </div>
</div> </div>
<div class="col-md-6" id="wrap-coverage"> <div class="col-md-4" id="wrap-coverage">
<div class="d-flex align-items-center gap-1 mb-1"> <div class="d-flex align-items-center gap-1 mb-1">
<label asp-for="CoverageSqFtPerLb" class="form-label mb-0">Coverage (@ViewBag.CoverageUnit)</label> <label asp-for="CoverageSqFtPerLb" class="form-label mb-0">Coverage (@ViewBag.CoverageUnit)</label>
<a tabindex="0" class="help-icon" role="button" <a tabindex="0" class="help-icon" role="button"
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus" data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
data-bs-title="Coverage" data-bs-title="Coverage"
data-bs-content="How many square feet one pound of this powder covers at a standard film thickness. Industry average is about 30 sq ft/lb. Check the manufacturer's technical data sheet for the exact value. This is used together with Transfer Efficiency to calculate how much powder to order for a job."> data-bs-content="Manufacturer theoretical coverage for this powder, typically based on about 1.5 mil film thickness. Many powders land around 70 to 120 sq ft/lb. Used together with Transfer Efficiency to calculate how much powder to order for a job.">
<i class="bi bi-question-circle"></i> <i class="bi bi-question-circle"></i>
</a> </a>
</div> </div>
<input asp-for="CoverageSqFtPerLb" type="number" step="0.01" min="0" value="30" class="form-control" id="field-coverage" placeholder="30" /> <input asp-for="CoverageSqFtPerLb" type="number" step="0.01" min="0" value="30" class="form-control" id="field-coverage" placeholder="e.g., 78" />
<span asp-validation-for="CoverageSqFtPerLb" class="text-danger"></span> <span asp-validation-for="CoverageSqFtPerLb" class="text-danger"></span>
<small class="form-text text-muted">Surface area coverage per unit of weight (default: 30)</small> <small class="form-text text-muted">Theoretical coverage from the TDS, usually expressed in sq ft/lb</small>
</div> </div>
<div class="col-md-6" id="wrap-transfer"> <div class="col-md-4" id="wrap-specificgravity">
<div class="d-flex align-items-center gap-1 mb-1">
<label asp-for="SpecificGravity" class="form-label mb-0">Specific Gravity</label>
<a tabindex="0" class="help-icon" role="button"
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
data-bs-title="Specific Gravity"
data-bs-content="Specific gravity from the powder's technical data sheet. This is useful reference data on its own and can also be used to derive theoretical coverage when the TDS omits a direct coverage number.">
<i class="bi bi-question-circle"></i>
</a>
</div>
<input asp-for="SpecificGravity" type="number" step="0.01" min="0" class="form-control" id="field-specificgravity" placeholder="e.g., 1.65" />
<span asp-validation-for="SpecificGravity" class="text-danger"></span>
<small class="form-text text-muted">Store the TDS specific gravity for future reference and calculations</small>
</div>
<div class="col-md-4" id="wrap-transfer">
<div class="d-flex align-items-center gap-1 mb-1"> <div class="d-flex align-items-center gap-1 mb-1">
<label asp-for="TransferEfficiency" class="form-label mb-0">Transfer Efficiency (%)</label> <label asp-for="TransferEfficiency" class="form-label mb-0">Transfer Efficiency (%)</label>
<a tabindex="0" class="help-icon" role="button" <a tabindex="0" class="help-icon" role="button"
@@ -161,21 +161,35 @@
</button> </button>
</div> </div>
</div> </div>
<div class="col-md-6" id="wrap-coverage"> <div class="col-md-4" id="wrap-coverage">
<div class="d-flex align-items-center gap-1 mb-1"> <div class="d-flex align-items-center gap-1 mb-1">
<label asp-for="CoverageSqFtPerLb" class="form-label mb-0">Coverage (@ViewBag.CoverageUnit)</label> <label asp-for="CoverageSqFtPerLb" class="form-label mb-0">Coverage (@ViewBag.CoverageUnit)</label>
<a tabindex="0" class="help-icon" role="button" <a tabindex="0" class="help-icon" role="button"
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus" data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
data-bs-title="Coverage" data-bs-title="Coverage"
data-bs-content="How many square feet one pound covers at a standard film thickness. Industry average is about 30 sq ft/lb — check the manufacturer's tech data sheet for the exact figure. Used together with Transfer Efficiency to calculate powder to order for each job."> data-bs-content="Manufacturer theoretical coverage for this powder, typically based on about 1.5 mil film thickness. Many powders land around 70 to 120 sq ft/lb. Used together with Transfer Efficiency to calculate powder to order for each job.">
<i class="bi bi-question-circle"></i> <i class="bi bi-question-circle"></i>
</a> </a>
</div> </div>
<input asp-for="CoverageSqFtPerLb" type="number" step="0.01" min="0" class="form-control" id="field-coverage" placeholder="30" /> <input asp-for="CoverageSqFtPerLb" type="number" step="0.01" min="0" class="form-control" id="field-coverage" placeholder="e.g., 78" />
<span asp-validation-for="CoverageSqFtPerLb" class="text-danger"></span> <span asp-validation-for="CoverageSqFtPerLb" class="text-danger"></span>
<small class="form-text text-muted">Surface area coverage per unit of weight (default: 30)</small> <small class="form-text text-muted">Theoretical coverage from the TDS, usually expressed in sq ft/lb</small>
</div> </div>
<div class="col-md-6" id="wrap-transfer"> <div class="col-md-4" id="wrap-specificgravity">
<div class="d-flex align-items-center gap-1 mb-1">
<label asp-for="SpecificGravity" class="form-label mb-0">Specific Gravity</label>
<a tabindex="0" class="help-icon" role="button"
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
data-bs-title="Specific Gravity"
data-bs-content="Specific gravity from the powder's technical data sheet. This is useful reference data on its own and can also be used to derive theoretical coverage when the TDS omits a direct coverage number.">
<i class="bi bi-question-circle"></i>
</a>
</div>
<input asp-for="SpecificGravity" type="number" step="0.01" min="0" class="form-control" id="field-specificgravity" placeholder="e.g., 1.65" />
<span asp-validation-for="SpecificGravity" class="text-danger"></span>
<small class="form-text text-muted">Store the TDS specific gravity for future reference and calculations</small>
</div>
<div class="col-md-4" id="wrap-transfer">
<div class="d-flex align-items-center gap-1 mb-1"> <div class="d-flex align-items-center gap-1 mb-1">
<label asp-for="TransferEfficiency" class="form-label mb-0">Transfer Efficiency (%)</label> <label asp-for="TransferEfficiency" class="form-label mb-0">Transfer Efficiency (%)</label>
<a tabindex="0" class="help-icon" role="button" <a tabindex="0" class="help-icon" role="button"
@@ -48,7 +48,7 @@
return catId && coatingMap[String(catId)] === true; return catId && coatingMap[String(catId)] === true;
} }
const coatingOnlyFields = ['wrap-colorname', 'wrap-colorcode', 'wrap-finish', 'wrap-coverage', 'wrap-transfer']; const coatingOnlyFields = ['wrap-colorname', 'wrap-colorcode', 'wrap-finish', 'wrap-coverage', 'wrap-specificgravity', 'wrap-transfer'];
const colorNameLabel = document.querySelector('#wrap-colorname label'); const colorNameLabel = document.querySelector('#wrap-colorname label');
function updateCoatingVisibility(catId) { function updateCoatingVisibility(catId) {
@@ -403,6 +403,7 @@
// Product details // Product details
fillIf('field-finish', data.finish, 'Finish'); fillIf('field-finish', data.finish, 'Finish');
fillIf('field-coverage', data.coverageSqFtPerLb, 'Coverage'); fillIf('field-coverage', data.coverageSqFtPerLb, 'Coverage');
fillIf('field-specificgravity', data.specificGravity, 'Specific Gravity');
fillIf('field-transfer', data.transferEfficiency, 'Transfer Efficiency'); fillIf('field-transfer', data.transferEfficiency, 'Transfer Efficiency');
// Coating specs // Coating specs
@@ -84,6 +84,11 @@
<input asp-for="CoverageSqFtPerLb" class="form-control" id="field-coverage" /> <input asp-for="CoverageSqFtPerLb" class="form-control" id="field-coverage" />
<span asp-validation-for="CoverageSqFtPerLb" class="text-danger small"></span> <span asp-validation-for="CoverageSqFtPerLb" class="text-danger small"></span>
</div> </div>
<div class="col-md-3">
<label asp-for="SpecificGravity" class="form-label fw-medium"></label>
<input asp-for="SpecificGravity" class="form-control" id="field-specificgravity" />
<span asp-validation-for="SpecificGravity" class="text-danger small"></span>
</div>
<div class="col-md-3"> <div class="col-md-3">
<label asp-for="TransferEfficiency" class="form-label fw-medium"></label> <label asp-for="TransferEfficiency" class="form-label fw-medium"></label>
<input asp-for="TransferEfficiency" class="form-control" id="field-transfer" /> <input asp-for="TransferEfficiency" class="form-control" id="field-transfer" />
@@ -144,6 +144,7 @@
setIfEmpty('field-curetemp', item.cureTemperatureF, 'Cure Temp'); setIfEmpty('field-curetemp', item.cureTemperatureF, 'Cure Temp');
setIfEmpty('field-curetime', item.cureTimeMinutes, 'Cure Time'); setIfEmpty('field-curetime', item.cureTimeMinutes, 'Cure Time');
setIfEmpty('field-coverage', item.coverageSqFtPerLb, 'Coverage'); setIfEmpty('field-coverage', item.coverageSqFtPerLb, 'Coverage');
setIfEmpty('field-specificgravity', item.specificGravity, 'Specific Gravity');
setIfEmpty('field-transfer', item.transferEfficiency,'Transfer Efficiency'); setIfEmpty('field-transfer', item.transferEfficiency,'Transfer Efficiency');
if (item.requiresClearCoat != null) { if (item.requiresClearCoat != null) {
@@ -246,6 +247,7 @@
setIfEmpty('field-finish', data.finish, 'Finish'); setIfEmpty('field-finish', data.finish, 'Finish');
setIfEmpty('field-coverage', data.coverageSqFtPerLb, 'Coverage'); setIfEmpty('field-coverage', data.coverageSqFtPerLb, 'Coverage');
setIfEmpty('field-specificgravity', data.specificGravity, 'Specific Gravity');
setIfEmpty('field-transfer', data.transferEfficiency, 'Transfer Efficiency'); setIfEmpty('field-transfer', data.transferEfficiency, 'Transfer Efficiency');
setIfEmpty('field-curetemp', data.cureTemperatureF, 'Cure Temp'); setIfEmpty('field-curetemp', data.cureTemperatureF, 'Cure Temp');
setIfEmpty('field-curetime', data.cureTimeMinutes, 'Cure Time'); setIfEmpty('field-curetime', data.cureTimeMinutes, 'Cure Time');
@@ -446,6 +446,7 @@
setIfEmpty('field-curetemp', data.cureTemperatureF, 'Cure Temp'); setIfEmpty('field-curetemp', data.cureTemperatureF, 'Cure Temp');
setIfEmpty('field-curetime', data.cureTimeMinutes, 'Cure Time'); setIfEmpty('field-curetime', data.cureTimeMinutes, 'Cure Time');
setIfEmpty('field-coverage', data.coverageSqFtPerLb, 'Coverage'); setIfEmpty('field-coverage', data.coverageSqFtPerLb, 'Coverage');
setIfEmpty('field-specificgravity', data.specificGravity, 'Specific Gravity');
setIfEmpty('field-transfer', data.transferEfficiency, 'Transfer Efficiency'); setIfEmpty('field-transfer', data.transferEfficiency, 'Transfer Efficiency');
if (data.unitPrice > 0) { if (data.unitPrice > 0) {
@@ -89,6 +89,7 @@
fillIfEmpty('field-curetemp', data.cureTemperatureF, 'Cure Temp', filled); fillIfEmpty('field-curetemp', data.cureTemperatureF, 'Cure Temp', filled);
fillIfEmpty('field-curetime', data.cureTimeMinutes, 'Cure Time', filled); fillIfEmpty('field-curetime', data.cureTimeMinutes, 'Cure Time', filled);
fillIfEmpty('field-coverage', data.coverageSqFtPerLb, 'Coverage', filled); fillIfEmpty('field-coverage', data.coverageSqFtPerLb, 'Coverage', filled);
fillIfEmpty('field-specificgravity', data.specificGravity, 'Specific Gravity', filled);
fillIfEmpty('field-transfer', data.transferEfficiency, 'Transfer Efficiency', filled); fillIfEmpty('field-transfer', data.transferEfficiency, 'Transfer Efficiency', filled);
fillIfEmpty('field-producturl', data.specPageUrl, 'Product URL', filled); fillIfEmpty('field-producturl', data.specPageUrl, 'Product URL', filled);
fillIfEmpty('field-imageurl', data.imageUrl, 'Image URL', filled); fillIfEmpty('field-imageurl', data.imageUrl, 'Image URL', filled);