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,284 @@
|
||||
@model PowderCoating.Application.DTOs.Accounting.AccountLedgerDto
|
||||
@using PowderCoating.Core.Enums
|
||||
|
||||
@{
|
||||
ViewData["Title"] = $"Ledger — {Model.AccountNumber} {Model.Name}";
|
||||
ViewData["PageIcon"] = "bi-journal-text";
|
||||
ViewData["PageHelpTitle"] = "Account Ledger";
|
||||
ViewData["PageHelpContent"] = "A chronological list of every transaction posted to this account. Click any Reference to open the source record. Debit increases asset and expense accounts; credit increases liability, equity, and revenue accounts. Use the date range or quick buttons (This Month, YTD, etc.) to narrow the view.";
|
||||
|
||||
string typeColor = Model.AccountType switch
|
||||
{
|
||||
AccountType.Asset => "success",
|
||||
AccountType.Liability => "danger",
|
||||
AccountType.Equity => "primary",
|
||||
AccountType.Revenue => "info",
|
||||
AccountType.CostOfGoods => "warning",
|
||||
AccountType.Expense => "secondary",
|
||||
_ => "secondary"
|
||||
};
|
||||
|
||||
string typeIcon = Model.AccountType switch
|
||||
{
|
||||
AccountType.Asset => "bi-safe",
|
||||
AccountType.Liability => "bi-credit-card",
|
||||
AccountType.Equity => "bi-bar-chart-line",
|
||||
AccountType.Revenue => "bi-graph-up-arrow",
|
||||
AccountType.CostOfGoods => "bi-box-seam",
|
||||
AccountType.Expense => "bi-receipt-cutoff",
|
||||
_ => "bi-journal"
|
||||
};
|
||||
|
||||
string typeLabel = Model.AccountType.ToDisplayName();
|
||||
|
||||
// Derive from AccountSubType (more reliable than AccountType which users can misconfigure)
|
||||
bool normalDebitBalance =
|
||||
Model.AccountSubType == AccountSubType.Cash ||
|
||||
Model.AccountSubType == AccountSubType.Checking ||
|
||||
Model.AccountSubType == AccountSubType.Savings ||
|
||||
Model.AccountSubType == AccountSubType.AccountsReceivable ||
|
||||
Model.AccountSubType == AccountSubType.Inventory ||
|
||||
Model.AccountSubType == AccountSubType.FixedAsset ||
|
||||
Model.AccountSubType == AccountSubType.OtherCurrentAsset ||
|
||||
Model.AccountSubType == AccountSubType.OtherAsset ||
|
||||
Model.AccountSubType == AccountSubType.CostOfGoodsSold ||
|
||||
(int)Model.AccountSubType >= 50; // all Expense subtypes
|
||||
|
||||
string balanceLabel = normalDebitBalance ? "Debit Balance" : "Credit Balance";
|
||||
|
||||
// Quick-range helpers for the filter bar
|
||||
var today = DateTime.Today;
|
||||
var thisMonthFrom = new DateTime(today.Year, today.Month, 1).ToString("yyyy-MM-dd");
|
||||
var thisMonthTo = today.ToString("yyyy-MM-dd");
|
||||
var lastMonthFrom = new DateTime(today.Year, today.Month, 1).AddMonths(-1).ToString("yyyy-MM-dd");
|
||||
var lastMonthTo = new DateTime(today.Year, today.Month, 1).AddDays(-1).ToString("yyyy-MM-dd");
|
||||
var qtdFrom = new DateTime(today.Year, ((today.Month - 1) / 3) * 3 + 1, 1).ToString("yyyy-MM-dd");
|
||||
var ytdFrom = new DateTime(today.Year, 1, 1).ToString("yyyy-MM-dd");
|
||||
}
|
||||
|
||||
<!-- Breadcrumb -->
|
||||
<nav aria-label="breadcrumb" class="mb-3">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a asp-action="Index">Chart of Accounts</a></li>
|
||||
<li class="breadcrumb-item active">@Model.AccountNumber – @Model.Name</li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<!-- Account header -->
|
||||
<div class="d-flex align-items-center gap-3 mb-4">
|
||||
<div class="rounded-3 p-3 bg-@typeColor bg-opacity-10 text-@typeColor">
|
||||
<i class="bi @typeIcon fs-3"></i>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-muted mb-0">
|
||||
<span class="badge bg-@typeColor bg-opacity-75 me-1">@typeLabel</span>
|
||||
<span class="text-muted small">@Model.AccountSubType.ToDisplayName() · @balanceLabel</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="ms-auto">
|
||||
<a asp-action="Edit" asp-route-id="@Model.Id" class="btn btn-sm btn-outline-secondary">
|
||||
<i class="bi bi-pencil me-1"></i>Edit Account
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Date range filter -->
|
||||
<div class="card shadow-sm mb-4">
|
||||
<div class="card-body py-3">
|
||||
<form method="get" class="row g-2 align-items-end">
|
||||
<input type="hidden" name="id" value="@Model.Id" />
|
||||
<div class="col-auto">
|
||||
<label class="form-label form-label-sm mb-1">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 form-label-sm mb-1">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-primary btn-sm">
|
||||
<i class="bi bi-funnel me-1"></i>Filter
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-auto ms-2">
|
||||
<div class="btn-group btn-group-sm" role="group">
|
||||
<a href="@Url.Action("Ledger", new { id = Model.Id, from = thisMonthFrom, to = thisMonthTo })"
|
||||
class="btn btn-outline-secondary">This Month</a>
|
||||
<a href="@Url.Action("Ledger", new { id = Model.Id, from = lastMonthFrom, to = lastMonthTo })"
|
||||
class="btn btn-outline-secondary">Last Month</a>
|
||||
<a href="@Url.Action("Ledger", new { id = Model.Id, from = qtdFrom, to = thisMonthTo })"
|
||||
class="btn btn-outline-secondary">QTD</a>
|
||||
<a href="@Url.Action("Ledger", new { id = Model.Id, from = ytdFrom, to = thisMonthTo })"
|
||||
class="btn btn-outline-secondary">YTD</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Summary cards -->
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="card shadow-sm h-100 text-center">
|
||||
<div class="card-body py-3">
|
||||
<div class="h5 text-muted mb-1">@Model.OpeningBalance.ToString("C")</div>
|
||||
<div class="text-muted small">Opening Balance</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="card shadow-sm h-100 text-center">
|
||||
<div class="card-body py-3">
|
||||
@{
|
||||
var netChange = normalDebitBalance
|
||||
? Model.PeriodDebits - Model.PeriodCredits
|
||||
: Model.PeriodCredits - Model.PeriodDebits;
|
||||
var netColor = netChange >= 0 ? "success" : "danger";
|
||||
}
|
||||
<div class="h5 text-@netColor mb-1">@netChange.ToString("C")</div>
|
||||
<div class="text-muted small">Period Net Change</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="card shadow-sm h-100 text-center">
|
||||
<div class="card-body py-3">
|
||||
<div class="h5 text-success mb-1">@Model.PeriodDebits.ToString("C")</div>
|
||||
<div class="text-muted small">Total Debits</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="card shadow-sm h-100 text-center border-@typeColor border-opacity-50">
|
||||
<div class="card-body py-3">
|
||||
<div class="h5 fw-bold text-@typeColor mb-1">@Model.ClosingBalance.ToString("C")</div>
|
||||
<div class="text-muted small">Closing @balanceLabel</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Ledger table -->
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header d-flex align-items-center justify-content-between">
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<span class="fw-semibold">
|
||||
<i class="bi bi-journal-text me-1"></i>
|
||||
Transactions
|
||||
<span class="text-muted fw-normal small ms-1">
|
||||
@Model.From.ToString("MMM d") – @Model.To.ToString("MMM d, yyyy")
|
||||
</span>
|
||||
</span>
|
||||
<a tabindex="0" class="help-icon" role="button"
|
||||
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
|
||||
data-bs-title="Transaction Ledger"
|
||||
data-bs-content="Each row is one side of a double-entry posting. Reference links back to the source record (invoice, bill, expense, or payment). Debit and Credit show which side of the entry hit this account. Running Balance updates after each line. The Opening Balance row shows the balance brought forward from before the selected date range.">
|
||||
<i class="bi bi-question-circle"></i>
|
||||
</a>
|
||||
</div>
|
||||
<span class="badge bg-secondary">@Model.Entries.Count entries</span>
|
||||
</div>
|
||||
|
||||
@if (!Model.Entries.Any())
|
||||
{
|
||||
<div class="card-body text-center py-5 text-muted">
|
||||
<i class="bi bi-journal-x fs-1 d-block mb-2"></i>
|
||||
<p class="mb-0">No transactions found for this account in the selected period.</p>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover table-sm align-middle mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th style="width:105px">Date</th>
|
||||
<th style="width:160px">Reference</th>
|
||||
<th style="width:140px">Source</th>
|
||||
<th>Description</th>
|
||||
<th class="text-end" style="width:110px">Debit</th>
|
||||
<th class="text-end" style="width:110px">Credit</th>
|
||||
<th class="text-end" style="width:120px">Balance</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- Opening balance row -->
|
||||
<tr class="table-light">
|
||||
<td class="text-muted small">@Model.From.ToString("MM/dd/yyyy")</td>
|
||||
<td><span class="fw-medium text-muted">—</span></td>
|
||||
<td><span class="badge bg-dark-subtle text-dark">Opening Balance</span></td>
|
||||
<td class="text-muted small">Balance brought forward as of @Model.From.ToString("MMM d, yyyy")</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td class="text-end">
|
||||
<span class="fw-semibold @(Model.OpeningBalance < 0 ? "text-danger" : "")">
|
||||
@Model.OpeningBalance.ToString("C")
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
@foreach (var entry in Model.Entries)
|
||||
{
|
||||
<tr>
|
||||
<td class="text-muted small">@entry.Date.ToString("MM/dd/yyyy")</td>
|
||||
<td>
|
||||
@if (entry.LinkController != null && entry.LinkId.HasValue)
|
||||
{
|
||||
<a asp-controller="@entry.LinkController" asp-action="Details"
|
||||
asp-route-id="@entry.LinkId" class="text-decoration-none fw-medium">
|
||||
@entry.Reference
|
||||
</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="fw-medium">@entry.Reference</span>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
@{
|
||||
string sourceBadge = entry.Source switch
|
||||
{
|
||||
"Invoice" => "bg-info-subtle text-info",
|
||||
"Invoice Payment" => "bg-success-subtle text-success",
|
||||
"Customer Payment" => "bg-success-subtle text-success",
|
||||
"Bill" => "bg-warning-subtle text-warning",
|
||||
"Bill Payment" => "bg-danger-subtle text-danger",
|
||||
"Expense" => "bg-secondary-subtle text-secondary",
|
||||
"Sales Tax" => "bg-primary-subtle text-primary",
|
||||
_ => "bg-secondary-subtle text-secondary"
|
||||
};
|
||||
}
|
||||
<span class="badge @sourceBadge">@entry.Source</span>
|
||||
</td>
|
||||
<td class="text-muted small">@entry.Description</td>
|
||||
<td class="text-end">
|
||||
@if (entry.Debit > 0)
|
||||
{
|
||||
<span class="text-success fw-medium">@entry.Debit.ToString("C")</span>
|
||||
}
|
||||
</td>
|
||||
<td class="text-end">
|
||||
@if (entry.Credit > 0)
|
||||
{
|
||||
<span class="text-danger fw-medium">@entry.Credit.ToString("C")</span>
|
||||
}
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<span class="fw-semibold @(entry.RunningBalance < 0 ? "text-danger" : "")">
|
||||
@entry.RunningBalance.ToString("C")
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
<tfoot class="table-light fw-semibold">
|
||||
<tr>
|
||||
<td colspan="4" class="text-muted small">Period Totals</td>
|
||||
<td class="text-end text-success">@Model.PeriodDebits.ToString("C")</td>
|
||||
<td class="text-end text-danger">@Model.PeriodCredits.ToString("C")</td>
|
||||
<td class="text-end">@Model.ClosingBalance.ToString("C")</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user