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,193 @@
|
||||
@model PowderCoating.Core.Entities.JobTemplate
|
||||
@{
|
||||
ViewData["Title"] = $"Template: {Model.Name}";
|
||||
ViewData["PageIcon"] = "bi-layout-text-window-reverse";
|
||||
}
|
||||
|
||||
<div class="d-flex justify-content-end align-items-center gap-2 mb-4">
|
||||
<a asp-action="Edit" asp-route-id="@Model.Id" class="btn btn-warning">
|
||||
<i class="bi bi-pencil me-2"></i>Edit
|
||||
</a>
|
||||
<a asp-action="Index" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-arrow-left me-2"></i>Back to Templates
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@if (TempData["Success"] != null)
|
||||
{
|
||||
<div class="alert alert-success alert-dismissible fade show">
|
||||
<i class="bi bi-check-circle me-2"></i>@TempData["Success"]
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="col-lg-8">
|
||||
<!-- Items -->
|
||||
<div class="card border-0 shadow-sm mb-4">
|
||||
<div class="card-header bg-white border-0 py-3">
|
||||
<h5 class="mb-0 fw-semibold">
|
||||
<i class="bi bi-stack me-2 text-primary"></i>Items
|
||||
<span class="badge bg-secondary ms-2">@Model.Items.Count(i => !i.IsDeleted)</span>
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
@if (!Model.Items.Any(i => !i.IsDeleted))
|
||||
{
|
||||
<p class="text-muted p-4 mb-0">No items on this template.</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>Description</th>
|
||||
<th class="text-center">Qty</th>
|
||||
<th>Surface Area</th>
|
||||
<th>Coatings</th>
|
||||
<th>Prep</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var item in Model.Items.Where(i => !i.IsDeleted).OrderBy(i => i.DisplayOrder))
|
||||
{
|
||||
<tr>
|
||||
<td>
|
||||
<div class="fw-semibold">@item.Description</div>
|
||||
@if (item.IsLaborItem)
|
||||
{
|
||||
<span class="badge bg-info-subtle text-info border border-info-subtle" style="font-size:.7rem">Labor</span>
|
||||
}
|
||||
@if (item.ManualUnitPrice.HasValue)
|
||||
{
|
||||
<small class="text-muted">Manual price: @item.ManualUnitPrice.Value.ToString("C")</small>
|
||||
}
|
||||
</td>
|
||||
<td class="text-center">@((int)item.Quantity)</td>
|
||||
<td>
|
||||
@if (item.SurfaceAreaSqFt > 0)
|
||||
{
|
||||
@item.SurfaceAreaSqFt.ToString("0.##") <small class="text-muted">sq ft</small>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="text-muted">—</span>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
@foreach (var coat in item.Coats.Where(c => !c.IsDeleted).OrderBy(c => c.Sequence))
|
||||
{
|
||||
<div class="small">
|
||||
<i class="bi bi-circle-fill me-1" style="color:#a371f7;font-size:.55rem"></i>
|
||||
@coat.CoatName
|
||||
@if (!string.IsNullOrEmpty(coat.ColorName))
|
||||
{
|
||||
<span class="text-muted">— @coat.ColorName</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@if (!item.Coats.Any(c => !c.IsDeleted))
|
||||
{
|
||||
<span class="text-muted small">—</span>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
@if (item.RequiresSandblasting)
|
||||
{
|
||||
<span class="badge bg-warning-subtle text-warning border border-warning-subtle me-1" style="font-size:.7rem">
|
||||
<i class="bi bi-tornado me-1"></i>Sandblast
|
||||
</span>
|
||||
}
|
||||
@if (item.RequiresMasking)
|
||||
{
|
||||
<span class="badge bg-warning-subtle text-warning border border-warning-subtle" style="font-size:.7rem">
|
||||
<i class="bi bi-mask me-1"></i>Masking
|
||||
</span>
|
||||
}
|
||||
@foreach (var prep in item.PrepServices.Where(p => !p.IsDeleted))
|
||||
{
|
||||
<div class="small text-muted">@prep.PrepService?.ServiceName</div>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Create Job from this Template -->
|
||||
<div class="card border-0 shadow-sm border-start border-4 border-primary">
|
||||
<div class="card-body d-flex align-items-center justify-content-between gap-3">
|
||||
<div>
|
||||
<h6 class="fw-bold mb-1"><i class="bi bi-plus-circle me-2 text-primary"></i>Use This Template</h6>
|
||||
<p class="text-muted mb-0 small">Create a new job pre-populated with this template's items and coatings.</p>
|
||||
</div>
|
||||
<a asp-controller="Jobs" asp-action="Create" asp-route-templateId="@Model.Id"
|
||||
class="btn btn-primary flex-shrink-0">
|
||||
<i class="bi bi-briefcase me-2"></i>Create Job
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-4">
|
||||
<!-- Template Info -->
|
||||
<div class="card border-0 shadow-sm mb-4">
|
||||
<div class="card-header bg-white border-0 py-3">
|
||||
<h5 class="mb-0 fw-semibold">
|
||||
<i class="bi bi-info-circle me-2 text-primary"></i>Template Info
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<dl class="row g-2 mb-0">
|
||||
<dt class="col-5 text-muted small">Status</dt>
|
||||
<dd class="col-7">
|
||||
@if (Model.IsActive)
|
||||
{
|
||||
<span class="badge bg-success">Active</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="badge bg-secondary">Inactive</span>
|
||||
}
|
||||
</dd>
|
||||
|
||||
<dt class="col-5 text-muted small">Customer</dt>
|
||||
<dd class="col-7">
|
||||
@if (Model.Customer != null)
|
||||
{
|
||||
<span>@(Model.Customer.CompanyName ?? $"{Model.Customer.ContactFirstName} {Model.Customer.ContactLastName}".Trim())</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="text-muted">Any customer</span>
|
||||
}
|
||||
</dd>
|
||||
|
||||
<dt class="col-5 text-muted small">Used</dt>
|
||||
<dd class="col-7">@Model.UsageCount time@(Model.UsageCount != 1 ? "s" : "")</dd>
|
||||
|
||||
<dt class="col-5 text-muted small">Created</dt>
|
||||
<dd class="col-7">@Model.CreatedAt.ToString("MMM d, yyyy")</dd>
|
||||
</dl>
|
||||
|
||||
@if (!string.IsNullOrEmpty(Model.Description))
|
||||
{
|
||||
<hr>
|
||||
<p class="small mb-0 text-muted">@Model.Description</p>
|
||||
}
|
||||
|
||||
@if (!string.IsNullOrEmpty(Model.SpecialInstructions))
|
||||
{
|
||||
<hr>
|
||||
<p class="small fw-semibold mb-1"><i class="bi bi-exclamation-circle text-warning me-1"></i>Special Instructions</p>
|
||||
<p class="small mb-0">@Model.SpecialInstructions</p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
@model List<PowderCoating.Core.Entities.JobTemplate>
|
||||
@{
|
||||
ViewData["Title"] = "Job Templates";
|
||||
ViewData["PageIcon"] = "bi-layout-text-window-reverse";
|
||||
}
|
||||
|
||||
<div class="d-flex justify-content-end align-items-center mb-4">
|
||||
<a asp-controller="Jobs" asp-action="Index" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-arrow-left me-2"></i>Back to Jobs
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@if (TempData["Success"] != null)
|
||||
{
|
||||
<div class="alert alert-success alert-dismissible fade show">
|
||||
<i class="bi bi-check-circle me-2"></i>@TempData["Success"]
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (!Model.Any())
|
||||
{
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-body text-center py-5">
|
||||
<i class="bi bi-layout-text-window-reverse text-muted" style="font-size:3rem"></i>
|
||||
<h5 class="mt-3 text-muted">No Templates Yet</h5>
|
||||
<p class="text-muted mb-4">Save a job as a template from the Job Details page to get started.</p>
|
||||
<a asp-controller="Jobs" asp-action="Index" class="btn btn-primary">
|
||||
<i class="bi bi-briefcase me-2"></i>Go to Jobs
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="row g-3">
|
||||
@foreach (var template in Model)
|
||||
{
|
||||
var itemCount = template.Items.Count(i => !i.IsDeleted);
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<div class="card border-0 shadow-sm h-100 @(template.IsActive ? "" : "opacity-50")">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-start mb-2">
|
||||
<h6 class="fw-bold mb-0">@template.Name</h6>
|
||||
@if (!template.IsActive)
|
||||
{
|
||||
<span class="badge bg-secondary">Inactive</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="badge bg-success-subtle text-success border border-success-subtle">Active</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (!string.IsNullOrEmpty(template.Description))
|
||||
{
|
||||
<p class="text-muted small mb-2">@template.Description</p>
|
||||
}
|
||||
|
||||
<div class="d-flex flex-wrap gap-2 mb-3">
|
||||
@if (template.Customer != null)
|
||||
{
|
||||
<span class="badge bg-light text-dark border">
|
||||
<i class="bi bi-building me-1"></i>
|
||||
@(template.Customer.CompanyName ?? $"{template.Customer.ContactFirstName} {template.Customer.ContactLastName}".Trim())
|
||||
</span>
|
||||
}
|
||||
<span class="badge bg-light text-dark border">
|
||||
<i class="bi bi-stack me-1"></i>@itemCount item@(itemCount != 1 ? "s" : "")
|
||||
</span>
|
||||
@if (template.UsageCount > 0)
|
||||
{
|
||||
<span class="badge bg-light text-dark border" title="Times used to create a job">
|
||||
<i class="bi bi-arrow-repeat me-1"></i>Used @template.UsageCount×
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="d-flex gap-2">
|
||||
<a asp-action="Details" asp-route-id="@template.Id" class="btn btn-sm btn-outline-primary flex-fill">
|
||||
<i class="bi bi-eye me-1"></i>View
|
||||
</a>
|
||||
<a asp-action="Edit" asp-route-id="@template.Id" class="btn btn-sm btn-outline-secondary">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</a>
|
||||
<form asp-action="Delete" asp-route-id="@template.Id" method="post" class="d-inline"
|
||||
onsubmit="return confirm('Delete template \'@template.Name\'?')">
|
||||
@Html.AntiForgeryToken()
|
||||
<button type="submit" class="btn btn-sm btn-outline-danger">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer bg-transparent border-0 pt-0 pb-3 px-3">
|
||||
<small class="text-muted">Created @template.CreatedAt.ToString("MMM d, yyyy")</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user