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:
2026-05-14 20:09:22 -04:00
parent 3eda91f170
commit 4ec55e7290
240 changed files with 73116 additions and 0 deletions
@@ -0,0 +1,126 @@
@using PowderCoating.Application.DTOs.Wizard
@model WizardStep3Dto
@{
ViewData["Title"] = "Setup Wizard — Document Numbering";
var progress = ViewBag.Progress as WizardProgressDto ?? new WizardProgressDto();
int step = ViewBag.Step as int? ?? 5;
}
@section Styles { @await Html.PartialAsync("_WizardStyles") }
<div class="wizard-layout">
@await Html.PartialAsync("_WizardProgress", progress)
<div class="wizard-content">
<div class="wizard-step-header">
<span class="wizard-step-badge">Step @step of @WizardProgressDto.TotalSteps</span>
<h2><i class="bi bi-palette me-2"></i>Document Numbering &amp; Branding</h2>
<p class="text-secondary">Customize how your document numbers are formatted and choose the accent color for each PDF type.</p>
</div>
<form asp-action="PostStep5" method="post">
@Html.AntiForgeryToken()
<div class="wizard-card">
<h5 class="wizard-card-title">Document Prefixes</h5>
<p class="text-secondary small mb-3">
Numbers are generated automatically in the format <strong>PREFIX-YYMM-####</strong> — for example, <code>QT-2604-0001</code> or <code>JOB-2604-0001</code>.
You can use any short prefix (up to 10 characters) that makes sense for your business.
</p>
<div class="row g-3">
<div class="col-md-4">
<label asp-for="QuoteNumberPrefix" class="form-label fw-semibold"></label>
<input asp-for="QuoteNumberPrefix" class="form-control" maxlength="10" placeholder="QT" />
<div class="form-text">e.g. <code>QT-2604-0001</code></div>
<span asp-validation-for="QuoteNumberPrefix" class="text-danger small"></span>
</div>
<div class="col-md-4">
<label asp-for="JobNumberPrefix" class="form-label fw-semibold"></label>
<input asp-for="JobNumberPrefix" class="form-control" maxlength="10" placeholder="JOB" />
<div class="form-text">e.g. <code>JOB-2604-0001</code></div>
<span asp-validation-for="JobNumberPrefix" class="text-danger small"></span>
</div>
<div class="col-md-4">
<label asp-for="InvoiceNumberPrefix" class="form-label fw-semibold"></label>
<input asp-for="InvoiceNumberPrefix" class="form-control" maxlength="10" placeholder="INV" />
<div class="form-text">e.g. <code>INV-2604-0001</code></div>
<span asp-validation-for="InvoiceNumberPrefix" class="text-danger small"></span>
</div>
</div>
</div>
<div class="wizard-card">
<h5 class="wizard-card-title">PDF Accent Colors</h5>
<p class="text-secondary small mb-3">
Choose the header accent color for each document type. Each can be set independently to match your brand or to visually distinguish document types.
</p>
<div class="row g-4">
<div class="col-md-4">
<label asp-for="QtAccentColor" class="form-label fw-semibold"></label>
<div class="d-flex align-items-center gap-2 mb-2">
<input asp-for="QtAccentColor" class="form-control form-control-color" type="color"
style="width:60px;height:38px;" oninput="syncColor('QtAccentColor','qtHex','qtPreview')" />
<input type="text" id="qtHex" class="form-control" style="width:110px;" maxlength="7"
value="@Model.QtAccentColor" placeholder="#374151"
oninput="syncHex(this,'QtAccentColor','qtPreview')" />
</div>
<div id="qtPreview" class="px-3 py-2 rounded text-white small fw-semibold"
style="background:@Model.QtAccentColor; min-width:140px;">
Quote Header
</div>
<span asp-validation-for="QtAccentColor" class="text-danger small"></span>
</div>
<div class="col-md-4">
<label asp-for="InAccentColor" class="form-label fw-semibold"></label>
<div class="d-flex align-items-center gap-2 mb-2">
<input asp-for="InAccentColor" class="form-control form-control-color" type="color"
style="width:60px;height:38px;" oninput="syncColor('InAccentColor','inHex','inPreview')" />
<input type="text" id="inHex" class="form-control" style="width:110px;" maxlength="7"
value="@Model.InAccentColor" placeholder="#374151"
oninput="syncHex(this,'InAccentColor','inPreview')" />
</div>
<div id="inPreview" class="px-3 py-2 rounded text-white small fw-semibold"
style="background:@Model.InAccentColor; min-width:140px;">
Invoice Header
</div>
<span asp-validation-for="InAccentColor" class="text-danger small"></span>
</div>
<div class="col-md-4">
<label asp-for="WoAccentColor" class="form-label fw-semibold"></label>
<div class="d-flex align-items-center gap-2 mb-2">
<input asp-for="WoAccentColor" class="form-control form-control-color" type="color"
style="width:60px;height:38px;" oninput="syncColor('WoAccentColor','woHex','woPreview')" />
<input type="text" id="woHex" class="form-control" style="width:110px;" maxlength="7"
value="@Model.WoAccentColor" placeholder="#374151"
oninput="syncHex(this,'WoAccentColor','woPreview')" />
</div>
<div id="woPreview" class="px-3 py-2 rounded text-white small fw-semibold"
style="background:@Model.WoAccentColor; min-width:140px;">
Work Order Header
</div>
<span asp-validation-for="WoAccentColor" class="text-danger small"></span>
</div>
</div>
</div>
@await Html.PartialAsync("_WizardFooter", step)
</form>
</div>
</div>
@section Scripts {
<script>
function syncColor(inputId, hexId, previewId) {
var val = document.getElementById(inputId).value;
document.getElementById(hexId).value = val;
document.getElementById(previewId).style.background = val;
}
function syncHex(input, colorId, previewId) {
var val = input.value;
if (/^#[0-9A-Fa-f]{6}$/.test(val)) {
document.getElementById(colorId).value = val;
document.getElementById(previewId).style.background = val;
}
}
</script>
}