Restore all zeroed views + add bulk gift certificate creation
The HTML entity sweep script had a bug where it wrote empty files for any
view that contained no target Unicode characters, zeroing out 215 view files.
All views restored from the pre-sweep commit (cefdf3e).
Bulk gift certificate feature:
- BulkCreateGiftCertificateDto with Quantity (1-500), Amount, Reason, Expiry, Notes
- GenerateBulkGiftCertificatePdfAsync on IPdfService / PdfService: one Letter page
per cert, reusing the same purple/gold branded ComposeGiftCertificateContent helper
- GiftCertificatesController: BulkCreate GET/POST, BulkResult GET, BulkDownloadPdf POST
- Views: BulkCreate.cshtml (form with live total preview), BulkResult.cshtml (table +
Download All PDF button that POSTs cert IDs to avoid URL length limits)
- gift-certificate-bulk.js: live preview + spinner/disable on submit
- Index.cshtml: Bulk Create button added alongside New Certificate
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,262 @@
|
||||
@model (int ItemIndex, List<PowderCoating.Application.DTOs.Quote.CreateQuoteItemCoatDto> Coats)
|
||||
|
||||
@{
|
||||
var itemIndex = Model.ItemIndex;
|
||||
var coats = Model.Coats ?? new List<PowderCoating.Application.DTOs.Quote.CreateQuoteItemCoatDto>();
|
||||
|
||||
// Debug: Log coat count
|
||||
var coatCountForDebug = coats.Count;
|
||||
|
||||
// If no coats exist, initialize with one default Single Stage coat
|
||||
if (!coats.Any())
|
||||
{
|
||||
coats.Add(new PowderCoating.Application.DTOs.Quote.CreateQuoteItemCoatDto
|
||||
{
|
||||
CoatName = "Single Stage",
|
||||
Sequence = 1,
|
||||
CoverageSqFtPerLb = 30,
|
||||
TransferEfficiency = 65
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
<!-- DEBUG: Item @itemIndex has @coats.Count coat(s) (original count: @coatCountForDebug) -->
|
||||
|
||||
<div class="row g-3 mb-3">
|
||||
<div class="col-12">
|
||||
<label class="form-label fw-semibold d-block mb-2">
|
||||
<i class="bi bi-paint-bucket me-1"></i>Coating Layers
|
||||
</label>
|
||||
<div class="accordion" id="coatsAccordion_@itemIndex">
|
||||
@for (int coatIndex = 0; coatIndex < coats.Count; coatIndex++)
|
||||
{
|
||||
var coat = coats[coatIndex];
|
||||
var isExpanded = coatIndex == 0; // First coat expanded by default
|
||||
|
||||
<div class="accordion-item coat-accordion-item" data-coat-index="@coatIndex">
|
||||
<h2 class="accordion-header" id="coatHeading_@(itemIndex)_@(coatIndex)">
|
||||
<div class="d-flex align-items-center">
|
||||
<button class="accordion-button @(isExpanded ? "" : "collapsed") flex-grow-1" type="button"
|
||||
data-bs-toggle="collapse" data-bs-target="#coat_@(itemIndex)_@(coatIndex)"
|
||||
aria-expanded="@(isExpanded ? "true" : "false")" aria-controls="coat_@(itemIndex)_@(coatIndex)">
|
||||
<span class="coat-header-text">@(coat.CoatName ?? $"Coating Layer {coatIndex + 1}")</span>
|
||||
</button>
|
||||
@if (coats.Count > 1)
|
||||
{
|
||||
<button type="button" class="btn btn-sm btn-outline-danger ms-2 me-2"
|
||||
onclick="removeCoat(@itemIndex, @coatIndex); event.stopPropagation();"
|
||||
title="Remove this coating layer">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</h2>
|
||||
<div id="coat_@(itemIndex)_@(coatIndex)" class="accordion-collapse collapse @(isExpanded ? "show" : "")"
|
||||
aria-labelledby="coatHeading_@(itemIndex)_@(coatIndex)"
|
||||
data-bs-parent="#coatsAccordion_@itemIndex">
|
||||
<div class="accordion-body">
|
||||
<input type="hidden" name="QuoteItems[@itemIndex].Coats[@coatIndex].Sequence" value="@coat.Sequence" />
|
||||
|
||||
<div class="row g-3 mb-3">
|
||||
<div class="col-md-12">
|
||||
<label class="form-label fw-semibold">Coat Type <span class="text-danger">*</span></label>
|
||||
<select name="QuoteItems[@itemIndex].Coats[@coatIndex].CoatName"
|
||||
class="form-select coat-name-input"
|
||||
onchange="updateCoatHeader(@itemIndex, @coatIndex)"
|
||||
required>
|
||||
<option value="Primer" selected="@(coat.CoatName == "Primer")">Primer</option>
|
||||
<option value="Single Stage" selected="@(coat.CoatName == "Single Stage")">Single Stage</option>
|
||||
<option value="Base Coat" selected="@(coat.CoatName == "Base Coat")">Base Coat</option>
|
||||
<option value="Top Coat" selected="@(coat.CoatName == "Top Coat")">Top Coat</option>
|
||||
<option value="Additional Stage" selected="@(coat.CoatName == "Additional Stage")">Additional Stage</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Radio: Stock vs Custom -->
|
||||
@{
|
||||
var hasInventoryItem = coat.InventoryItemId.HasValue && coat.InventoryItemId.Value > 0;
|
||||
var isCustom = !string.IsNullOrEmpty(coat.ColorName) || coat.PowderCostPerLb.HasValue;
|
||||
var coatType = hasInventoryItem ? "stock" : (isCustom ? "custom" : "stock");
|
||||
}
|
||||
<div class="btn-group mb-3 w-100" role="group">
|
||||
<input type="radio" class="btn-check"
|
||||
name="coatPowderType_@(itemIndex)_@(coatIndex)"
|
||||
id="coatStock_@(itemIndex)_@(coatIndex)" value="stock"
|
||||
@(coatType == "stock" ? "checked" : "")
|
||||
onchange="toggleCoatPowderSelection(@itemIndex, @coatIndex, 'stock')">
|
||||
<label class="btn btn-outline-primary" for="coatStock_@(itemIndex)_@(coatIndex)">
|
||||
<i class="bi bi-box-seam me-1"></i>In-Stock Powder
|
||||
</label>
|
||||
|
||||
<input type="radio" class="btn-check"
|
||||
name="coatPowderType_@(itemIndex)_@(coatIndex)"
|
||||
id="coatCustom_@(itemIndex)_@(coatIndex)" value="custom"
|
||||
@(coatType == "custom" ? "checked" : "")
|
||||
onchange="toggleCoatPowderSelection(@itemIndex, @coatIndex, 'custom')">
|
||||
<label class="btn btn-outline-primary" for="coatCustom_@(itemIndex)_@(coatIndex)">
|
||||
<i class="bi bi-bag-plus me-1"></i>Custom Powder
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Stock Powder Section -->
|
||||
<div id="coatStockSection_@(itemIndex)_@(coatIndex)" style="display: @(coatType == "stock" ? "block" : "none")">
|
||||
<div class="row g-3 mb-3">
|
||||
<div class="col-md-12">
|
||||
<label class="form-label">Select Powder from Inventory</label>
|
||||
<select name="QuoteItems[@itemIndex].Coats[@coatIndex].InventoryItemId"
|
||||
id="coatInventorySelect_@(itemIndex)_@(coatIndex)"
|
||||
class="form-select inventory-coat-select"
|
||||
data-selected-value="@(coat.InventoryItemId ?? 0)"
|
||||
onchange="onCoatInventorySelected(@itemIndex, @coatIndex)">
|
||||
<option value="">-- Select Powder --</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-3 mb-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Coverage Rate</label>
|
||||
<div class="input-group input-group-sm">
|
||||
<input type="number"
|
||||
name="QuoteItems[@itemIndex].Coats[@coatIndex].CoverageSqFtPerLb"
|
||||
id="coatCoverage_@(itemIndex)_@(coatIndex)"
|
||||
class="form-control" value="@coat.CoverageSqFtPerLb"
|
||||
min="1" max="500" step="0.1"
|
||||
onchange="calculateCoatPowderNeeded(@itemIndex, @coatIndex)">
|
||||
<span class="input-group-text">sq ft/lb</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Transfer Efficiency</label>
|
||||
<div class="input-group input-group-sm">
|
||||
<input type="number"
|
||||
name="QuoteItems[@itemIndex].Coats[@coatIndex].TransferEfficiency"
|
||||
id="coatEfficiency_@(itemIndex)_@(coatIndex)"
|
||||
class="form-control" value="@coat.TransferEfficiency"
|
||||
min="1" max="100" step="1"
|
||||
onchange="calculateCoatPowderNeeded(@itemIndex, @coatIndex)">
|
||||
<span class="input-group-text">%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Custom Powder Section -->
|
||||
<div id="coatCustomSection_@(itemIndex)_@(coatIndex)" style="display: @(coatType == "custom" ? "block" : "none")">
|
||||
<div class="row g-3 mb-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Color Name</label>
|
||||
<input type="text"
|
||||
name="QuoteItems[@itemIndex].Coats[@coatIndex].ColorName"
|
||||
id="coatColorName_@(itemIndex)_@(coatIndex)"
|
||||
value="@coat.ColorName"
|
||||
class="form-control"
|
||||
placeholder="e.g., Gloss Black RAL 9005"
|
||||
onchange="updateCoatHeader(@itemIndex, @coatIndex)" />
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Vendor</label>
|
||||
<select name="QuoteItems[@itemIndex].Coats[@coatIndex].VendorId"
|
||||
id="coatVendorSelect_@(itemIndex)_@(coatIndex)"
|
||||
class="form-select coat-vendor-select"
|
||||
data-selected-value="@(coat.VendorId ?? 0)">
|
||||
<option value="">-- Select Vendor --</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-3 mb-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Powder to Order</label>
|
||||
<div class="input-group input-group-sm">
|
||||
<input type="number"
|
||||
name="QuoteItems[@itemIndex].Coats[@coatIndex].PowderToOrder"
|
||||
id="coatPowderToOrder_@(itemIndex)_@(coatIndex)"
|
||||
value="@coat.PowderToOrder"
|
||||
class="form-control" min="0" step="1"
|
||||
placeholder="Auto-calculated" />
|
||||
<span class="input-group-text">lbs</span>
|
||||
</div>
|
||||
<small class="text-muted">Auto-filled from powder needed (rounded up)</small>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Powder Cost</label>
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="input-group-text">$</span>
|
||||
<input type="number"
|
||||
name="QuoteItems[@itemIndex].Coats[@coatIndex].PowderCostPerLb"
|
||||
value="@coat.PowderCostPerLb"
|
||||
class="form-control" min="0" step="0.01"
|
||||
placeholder="0.00" />
|
||||
<span class="input-group-text">per lb</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-3 mb-3">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Coverage Rate</label>
|
||||
<div class="input-group input-group-sm">
|
||||
<input type="number"
|
||||
name="QuoteItems[@itemIndex].Coats[@coatIndex].CoverageSqFtPerLb"
|
||||
id="coatCustomCoverage_@(itemIndex)_@(coatIndex)"
|
||||
class="form-control" value="@coat.CoverageSqFtPerLb"
|
||||
min="1" max="500" step="0.1"
|
||||
onchange="calculateCoatPowderNeeded(@itemIndex, @coatIndex)">
|
||||
<span class="input-group-text">sq ft/lb</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Transfer Efficiency</label>
|
||||
<div class="input-group input-group-sm">
|
||||
<input type="number"
|
||||
name="QuoteItems[@itemIndex].Coats[@coatIndex].TransferEfficiency"
|
||||
id="coatCustomEfficiency_@(itemIndex)_@(coatIndex)"
|
||||
class="form-control" value="@coat.TransferEfficiency"
|
||||
min="1" max="100" step="1"
|
||||
onchange="calculateCoatPowderNeeded(@itemIndex, @coatIndex)">
|
||||
<span class="input-group-text">%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Powder Needed Display (shown for calculated items only) -->
|
||||
<div class="row g-3 mb-3">
|
||||
<div class="col-md-12 coat-powder-needed-display" id="coatPowderNeededContainer_@(itemIndex)_@(coatIndex)">
|
||||
<label class="form-label fw-semibold">Powder Needed for This Coat (Total Batch)</label>
|
||||
<div class="bg-success bg-opacity-10 border border-success rounded p-3 text-center">
|
||||
<strong id="coatPowderNeeded_@(itemIndex)_@(coatIndex)" class="fs-5 text-success">0.00 lbs</strong>
|
||||
</div>
|
||||
<small class="text-muted">
|
||||
<i class="bi bi-info-circle"></i> This calculation is for the entire batch (all items × surface area)
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Coat Notes -->
|
||||
<div class="row g-3">
|
||||
<div class="col-md-12">
|
||||
<label class="form-label">Notes (Optional)</label>
|
||||
<textarea name="QuoteItems[@itemIndex].Coats[@coatIndex].Notes"
|
||||
class="form-control" rows="2"
|
||||
placeholder="Special instructions for this coating layer...">@coat.Notes</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Add Coating Layer Button -->
|
||||
<div class="mt-3">
|
||||
<button type="button" class="btn btn-sm btn-outline-success"
|
||||
onclick="addCoat(@itemIndex)">
|
||||
<i class="bi bi-plus-circle me-1"></i> Add Coating Layer
|
||||
</button>
|
||||
<small class="text-muted ms-2">
|
||||
<i class="bi bi-info-circle"></i>
|
||||
First coat = 100% labor, each additional = +30% labor
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user