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,224 @@
@model PowderCoating.Application.DTOs.Accounting.CashFlowStatementDto
@using PowderCoating.Core.Enums
@{
ViewData["Title"] = "Cash Flow Statement";
string AmountClass(decimal v) => v < 0 ? "text-danger" : "text-body";
string Fmt(decimal v) => v.ToString("C");
}
<div class="d-flex align-items-center justify-content-between mb-4">
<div>
<h4 class="fw-bold mb-0"><i class="bi bi-water me-2 text-info"></i>Cash Flow Statement</h4>
<p class="text-muted small mb-0">
@Model.From.ToString("MMMM d, yyyy") @Model.To.ToString("MMMM d, yyyy")
&nbsp;·&nbsp; Direct Method (Cash Basis)
</p>
</div>
<div class="d-flex gap-2">
<a asp-action="CashFlowStatementPdf"
asp-route-from="@Model.From.ToString("yyyy-MM-dd")"
asp-route-to="@Model.To.ToString("yyyy-MM-dd")"
class="btn btn-outline-secondary btn-sm">
<i class="bi bi-download me-1"></i>PDF
</a>
<a asp-action="CashFlowStatementPdf"
asp-route-from="@Model.From.ToString("yyyy-MM-dd")"
asp-route-to="@Model.To.ToString("yyyy-MM-dd")"
asp-route-inline="true"
target="_blank"
class="btn btn-outline-primary btn-sm">
<i class="bi bi-eye me-1"></i>Preview
</a>
</div>
</div>
<!-- Date range filter -->
<form method="get" asp-action="CashFlowStatement" class="card shadow-sm mb-4">
<div class="card-body">
<div class="row g-3 align-items-end">
<div class="col-auto">
<label class="form-label fw-semibold small">From</label>
<input type="date" name="from" class="form-control form-control-sm" value="@Model.From.ToString("yyyy-MM-dd")" />
</div>
<div class="col-auto">
<label class="form-label fw-semibold small">To</label>
<input type="date" name="to" class="form-control form-control-sm" value="@Model.To.ToString("yyyy-MM-dd")" />
</div>
<div class="col-auto">
<button type="submit" class="btn btn-sm btn-primary">Update</button>
</div>
<!-- Quick date presets -->
@{
var y = DateTime.Today.Year;
var presets = new[]
{
("YTD", new DateTime(y, 1, 1).ToString("yyyy-MM-dd"), DateTime.Today.ToString("yyyy-MM-dd")),
("This Qtr", new DateTime(y, ((DateTime.Today.Month - 1) / 3) * 3 + 1, 1).ToString("yyyy-MM-dd"), DateTime.Today.ToString("yyyy-MM-dd")),
("Last Year", new DateTime(y-1, 1, 1).ToString("yyyy-MM-dd"), new DateTime(y-1, 12, 31).ToString("yyyy-MM-dd")),
};
}
@foreach (var (label, f, t) in presets)
{
<div class="col-auto">
<a asp-action="CashFlowStatement" asp-route-from="@f" asp-route-to="@t"
class="btn btn-sm btn-outline-secondary">@label</a>
</div>
}
</div>
</div>
</form>
<div class="row g-4">
<!-- Main statement -->
<div class="col-lg-8">
<!-- Operating Activities -->
<div class="card shadow-sm mb-3">
<div class="card-header d-flex align-items-center justify-content-between">
<span class="fw-semibold"><i class="bi bi-gear me-2 text-info"></i>Operating Activities</span>
<span class="badge @(Model.NetOperating >= 0 ? "bg-success" : "bg-danger")">@Fmt(Model.NetOperating)</span>
</div>
<div class="card-body p-0">
<table class="table table-sm mb-0">
<tbody>
<tr>
<td class="ps-3 text-body-secondary">Cash received from customers</td>
<td class="text-end pe-3 text-success fw-semibold">@Fmt(Model.CashFromCustomers)</td>
</tr>
<tr>
<td class="ps-3 text-body-secondary">Cash paid to vendors (bills)</td>
<td class="text-end pe-3 @AmountClass(-Model.CashToVendors)">(@Fmt(Model.CashToVendors))</td>
</tr>
<tr>
<td class="ps-3 text-body-secondary">Cash paid for direct expenses</td>
<td class="text-end pe-3 @AmountClass(-Model.CashForExpenses)">(@Fmt(Model.CashForExpenses))</td>
</tr>
</tbody>
<tfoot class="table-light">
<tr>
<td class="ps-3 fw-semibold">Net Cash from Operating Activities</td>
<td class="text-end pe-3 fw-bold @AmountClass(Model.NetOperating)">@Fmt(Model.NetOperating)</td>
</tr>
</tfoot>
</table>
</div>
</div>
<!-- Investing Activities -->
<div class="card shadow-sm mb-3">
<div class="card-header d-flex align-items-center justify-content-between">
<span class="fw-semibold"><i class="bi bi-building me-2 text-primary"></i>Investing Activities</span>
<span class="badge @(Model.NetInvesting >= 0 ? "bg-success" : "bg-danger")">@Fmt(Model.NetInvesting)</span>
</div>
<div class="card-body p-0">
<table class="table table-sm mb-0">
<tbody>
@if (!Model.InvestingLines.Any())
{
<tr>
<td class="ps-3 text-muted" colspan="2">
<i class="bi bi-dash-circle me-1"></i>No investing activities recorded in this period.
</td>
</tr>
}
else
{
@foreach (var line in Model.InvestingLines)
{
<tr>
<td class="ps-3 text-body-secondary">@line.Label</td>
<td class="text-end pe-3 @AmountClass(line.Amount)">@Fmt(line.Amount)</td>
</tr>
}
}
</tbody>
<tfoot class="table-light">
<tr>
<td class="ps-3 fw-semibold">Net Cash from Investing Activities</td>
<td class="text-end pe-3 fw-bold @AmountClass(Model.NetInvesting)">@Fmt(Model.NetInvesting)</td>
</tr>
</tfoot>
</table>
</div>
</div>
<!-- Financing Activities -->
<div class="card shadow-sm mb-3">
<div class="card-header d-flex align-items-center justify-content-between">
<span class="fw-semibold"><i class="bi bi-bank me-2 text-secondary"></i>Financing Activities</span>
<span class="badge @(Model.NetFinancing >= 0 ? "bg-success" : "bg-danger")">@Fmt(Model.NetFinancing)</span>
</div>
<div class="card-body p-0">
<table class="table table-sm mb-0">
<tbody>
@if (!Model.FinancingLines.Any())
{
<tr>
<td class="ps-3 text-muted" colspan="2">
<i class="bi bi-dash-circle me-1"></i>No financing activities recorded in this period.
</td>
</tr>
}
else
{
@foreach (var line in Model.FinancingLines)
{
<tr>
<td class="ps-3 text-body-secondary">@line.Label</td>
<td class="text-end pe-3 @AmountClass(line.Amount)">@Fmt(line.Amount)</td>
</tr>
}
}
</tbody>
<tfoot class="table-light">
<tr>
<td class="ps-3 fw-semibold">Net Cash from Financing Activities</td>
<td class="text-end pe-3 fw-bold @AmountClass(Model.NetFinancing)">@Fmt(Model.NetFinancing)</td>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
<!-- Summary sidebar -->
<div class="col-lg-4">
<div class="card shadow-sm mb-3">
<div class="card-header fw-semibold"><i class="bi bi-calculator me-2 text-info"></i>Cash Summary</div>
<div class="card-body">
<dl class="row mb-0">
<dt class="col-8 text-muted small fw-normal">Beginning Cash</dt>
<dd class="col-4 text-end fw-semibold mb-2">@Fmt(Model.BeginningCash)</dd>
<dt class="col-8 text-muted small fw-normal">Operating</dt>
<dd class="col-4 text-end fw-semibold mb-1 @AmountClass(Model.NetOperating)">@Fmt(Model.NetOperating)</dd>
<dt class="col-8 text-muted small fw-normal">Investing</dt>
<dd class="col-4 text-end fw-semibold mb-1 @AmountClass(Model.NetInvesting)">@Fmt(Model.NetInvesting)</dd>
<dt class="col-8 text-muted small fw-normal">Financing</dt>
<dd class="col-4 text-end fw-semibold mb-2 @AmountClass(Model.NetFinancing)">@Fmt(Model.NetFinancing)</dd>
<dt class="col-8 text-muted small fw-normal">Net Change in Cash</dt>
<dd class="col-4 text-end fw-semibold mb-3 @AmountClass(Model.NetChangeInCash)">@Fmt(Model.NetChangeInCash)</dd>
<dt class="col-8 fw-bold">Ending Cash Balance</dt>
<dd class="col-4 text-end fw-bold fs-5 @AmountClass(Model.EndingCash)">@Fmt(Model.EndingCash)</dd>
</dl>
</div>
</div>
<div class="card shadow-sm">
<div class="card-header fw-semibold small"><i class="bi bi-info-circle me-2"></i>Methodology</div>
<div class="card-body small text-muted">
<p class="mb-2">This statement uses the <strong>direct (cash basis)</strong> method for Operating Activities:</p>
<ul class="mb-2 ps-3">
<li>Inflows = customer invoice payments received</li>
<li>Outflows = vendor bill payments + direct expense payments</li>
</ul>
<p class="mb-0">Beginning Cash is approximated from all cash inflows and outflows recorded prior to the start date plus account opening balances. For the most accurate beginning balance, reconcile your bank accounts first.</p>
</div>
</div>
</div>
</div>