Files
PowderCoatingLogix/src/PowderCoating.Web/Views/Reports/SalesTax.cshtml
T
spouliot 4ec55e7290 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>
2026-05-14 20:09:22 -04:00

370 lines
18 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
@model PowderCoating.Application.DTOs.Accounting.SalesTaxReportDto
@{
ViewData["Title"] = "Sales Tax Report";
ViewData["PageIcon"] = "bi-percent";
var today = DateTime.Today;
var ytdFrom = new DateTime(today.Year, 1, 1).ToString("yyyy-MM-dd");
var ytdTo = today.ToString("yyyy-MM-dd");
var lastYrFrom = new DateTime(today.Year - 1, 1, 1).ToString("yyyy-MM-dd");
var lastYrTo = new DateTime(today.Year - 1, 12, 31).ToString("yyyy-MM-dd");
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 monthLabels = Model.ByMonth.Select(m => m.Label).ToList();
var monthTaxable = Model.ByMonth.Select(m => m.TaxableSales).ToList();
var monthTaxBilled = Model.ByMonth.Select(m => m.TaxBilled).ToList();
}
<style>
@@media print {
.no-print { display: none !important; }
.card { border: 1px solid #dee2e6 !important; box-shadow: none !important; }
body { font-size: 11px; }
}
.row-nontaxable td { background-color: #f8f9fa !important; color: #6c757d; }
</style>
<!-- Header -->
<div class="d-flex align-items-center gap-2 mb-3 no-print">
<a asp-action="Index" class="btn btn-sm btn-outline-secondary"><i class="bi bi-arrow-left"></i></a>
<p class="text-muted mb-0">@Model.From.ToString("MMM d") @Model.To.ToString("MMM d, yyyy") · @(Model.TaxableInvoiceCount + Model.NonTaxableInvoiceCount) invoices</p>
<div class="ms-auto d-flex gap-2">
<a href="@Url.Action("SalesTaxCsv", new { from = Model.From.ToString("yyyy-MM-dd"), to = Model.To.ToString("yyyy-MM-dd") })"
class="btn btn-sm btn-outline-success no-print">
<i class="bi bi-filetype-csv me-1"></i>Export CSV
</a>
<a href="@Url.Action("SalesTaxPdf", new { from = Model.From.ToString("yyyy-MM-dd"), to = Model.To.ToString("yyyy-MM-dd") })"
class="btn btn-sm btn-outline-danger no-print" target="_blank">
<i class="bi bi-file-pdf me-1"></i>Download PDF
</a>
<a href="@Url.Action("SalesTaxPdf", new { from = Model.From.ToString("yyyy-MM-dd"), to = Model.To.ToString("yyyy-MM-dd"), inline = true })"
class="btn btn-sm btn-outline-secondary no-print" target="_blank">
<i class="bi bi-printer me-1"></i>Print
</a>
</div>
</div>
<!-- Date filter -->
<div class="card shadow-sm mb-4 no-print">
<div class="card-body py-3">
<form method="get" class="row g-2 align-items-end">
<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>Run Report</button>
</div>
<div class="col-auto ms-2">
<div class="btn-group btn-group-sm">
<a href="@Url.Action("SalesTax", new { from = thisMonthFrom, to = thisMonthTo })" class="btn btn-outline-secondary">This Month</a>
<a href="@Url.Action("SalesTax", new { from = lastMonthFrom, to = lastMonthTo })" class="btn btn-outline-secondary">Last Month</a>
<a href="@Url.Action("SalesTax", new { from = ytdFrom, to = ytdTo })" class="btn btn-outline-secondary">YTD</a>
<a href="@Url.Action("SalesTax", new { from = lastYrFrom, to = lastYrTo })" class="btn btn-outline-secondary">Last Year</a>
</div>
</div>
</form>
</div>
</div>
<!-- Print header -->
<div class="text-center mb-4 d-none d-print-block">
<h4 class="fw-bold">@Model.CompanyName</h4>
<h5>Sales Tax Liability Report</h5>
<p class="text-muted">@Model.From.ToString("MMMM d, yyyy") @Model.To.ToString("MMMM d, yyyy") · Invoice Basis</p>
</div>
<!-- KPI Cards -->
<div class="row g-3 mb-4">
<div class="col-6 col-md-3">
<div class="card shadow-sm text-center h-100 border-primary border-top border-3">
<div class="card-body py-3">
<div class="h5 fw-bold text-primary mb-1">@Model.TotalTaxBilled.ToString("C")</div>
<div class="text-muted small">Total Tax Billed</div>
</div>
</div>
</div>
<div class="col-6 col-md-3">
<div class="card shadow-sm text-center h-100">
<div class="card-body py-3">
<div class="h5 fw-bold mb-1">@Model.TotalTaxableSales.ToString("C")</div>
<div class="text-muted small">Taxable Sales</div>
<div class="text-muted" style="font-size:0.7rem">@Model.TaxableInvoiceCount invoices</div>
</div>
</div>
</div>
<div class="col-6 col-md-3">
<div class="card shadow-sm text-center h-100">
<div class="card-body py-3">
<div class="h5 fw-bold text-secondary mb-1">@Model.TotalNonTaxableSales.ToString("C")</div>
<div class="text-muted small">Non-Taxable Sales</div>
<div class="text-muted" style="font-size:0.7rem">@Model.NonTaxableInvoiceCount invoices</div>
</div>
</div>
</div>
<div class="col-6 col-md-3">
<div class="card shadow-sm text-center h-100">
<div class="card-body py-3">
<div class="h5 fw-bold text-success mb-1">@Model.EffectiveTaxRate.ToString("F2")%</div>
<div class="text-muted small">Effective Tax Rate</div>
<div class="text-muted" style="font-size:0.7rem">on taxable sales</div>
</div>
</div>
</div>
</div>
@if (!Model.Invoices.Any())
{
<div class="card shadow-sm">
<div class="card-body text-center py-5 text-muted">
<i class="bi bi-receipt fs-1 d-block mb-2"></i>
<p class="mb-0">No invoices found for this period.</p>
</div>
</div>
}
else
{
<div class="row g-4 mb-4">
<!-- Monthly trend chart -->
@if (Model.ByMonth.Count > 1)
{
<div class="col-lg-8 no-print">
<div class="card shadow-sm h-100">
<div class="card-header fw-semibold">
<i class="bi bi-bar-chart me-1"></i>Monthly Tax Trend
</div>
<div class="card-body">
<canvas id="taxTrendChart" height="120"></canvas>
</div>
</div>
</div>
}
<!-- By Month table -->
<div class="col-lg-@(Model.ByMonth.Count > 1 ? "4" : "12")">
<div class="card shadow-sm h-100">
<div class="card-header fw-semibold"><i class="bi bi-calendar3 me-1"></i>By Month</div>
<div class="table-responsive">
<table class="table table-sm table-hover align-middle mb-0">
<thead class="table-light">
<tr>
<th>Month</th>
<th class="text-end">Taxable Sales</th>
<th class="text-end">Tax Billed</th>
<th class="text-center">#</th>
</tr>
</thead>
<tbody>
@foreach (var m in Model.ByMonth)
{
<tr>
<td>@m.Label</td>
<td class="text-end">@m.TaxableSales.ToString("C")</td>
<td class="text-end text-primary fw-semibold">@m.TaxBilled.ToString("C")</td>
<td class="text-center text-muted small">@m.InvoiceCount</td>
</tr>
}
</tbody>
<tfoot class="table-light fw-semibold">
<tr>
<td>Total</td>
<td class="text-end">@Model.TotalTaxableSales.ToString("C")</td>
<td class="text-end text-primary">@Model.TotalTaxBilled.ToString("C")</td>
<td class="text-center">@Model.TaxableInvoiceCount</td>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
</div>
<!-- By Tax Account -->
@if (Model.ByAccount.Any())
{
<div class="card shadow-sm mb-4">
<div class="card-header fw-semibold"><i class="bi bi-bookmark me-1"></i>By Tax Account</div>
<div class="table-responsive">
<table class="table table-sm table-hover align-middle mb-0">
<thead class="table-light">
<tr>
<th>Account</th>
<th class="text-end">Taxable Sales</th>
<th class="text-end">Tax Billed</th>
<th class="text-center">Invoices</th>
<th class="text-end no-print">Effective Rate</th>
</tr>
</thead>
<tbody>
@foreach (var a in Model.ByAccount)
{
var rate = a.TaxableSales == 0 ? 0m : Math.Round(a.TaxBilled / a.TaxableSales * 100, 2);
<tr>
<td>
@if (!string.IsNullOrEmpty(a.AccountNumber))
{
<span class="text-muted small me-1">@a.AccountNumber</span>
}
@a.AccountName
</td>
<td class="text-end">@a.TaxableSales.ToString("C")</td>
<td class="text-end fw-semibold text-primary">@a.TaxBilled.ToString("C")</td>
<td class="text-center text-muted small">@a.InvoiceCount</td>
<td class="text-end text-muted small no-print">@rate.ToString("F2")%</td>
</tr>
}
</tbody>
<tfoot class="table-light fw-semibold">
<tr>
<td>Total</td>
<td class="text-end">@Model.TotalTaxableSales.ToString("C")</td>
<td class="text-end text-primary">@Model.TotalTaxBilled.ToString("C")</td>
<td class="text-center">@Model.TaxableInvoiceCount</td>
<td class="no-print"></td>
</tr>
</tfoot>
</table>
</div>
</div>
}
<!-- Invoice Detail -->
<div class="card shadow-sm">
<div class="card-header d-flex justify-content-between align-items-center">
<span class="fw-semibold"><i class="bi bi-receipt me-1"></i>Invoice Detail</span>
<div class="d-flex align-items-center gap-2">
<span class="badge bg-secondary">@(Model.TaxableInvoiceCount + Model.NonTaxableInvoiceCount) invoices</span>
<span class="badge bg-light text-muted border">
<span class="d-inline-block me-1" style="width:10px;height:10px;background:#f8f9fa;border:1px solid #dee2e6"></span>
Non-taxable rows shaded
</span>
</div>
</div>
<div class="table-responsive">
<table class="table table-hover table-sm align-middle mb-0">
<thead class="table-light">
<tr>
<th>Invoice</th>
<th>Customer</th>
<th>Date</th>
<th>Status</th>
<th class="text-end">Subtotal</th>
<th class="text-end">Tax %</th>
<th class="text-end">Tax Amount</th>
<th class="text-end">Total</th>
<th class="text-end no-print">Paid</th>
<th>Tax Account</th>
</tr>
</thead>
<tbody>
@foreach (var inv in Model.Invoices)
{
bool isTaxable = inv.TaxAmount > 0;
string statusBadge = inv.Status switch
{
"Paid" => "bg-success-subtle text-success",
"PartiallyPaid" => "bg-warning-subtle text-warning",
"Sent" => "bg-info-subtle text-info",
"Overdue" => "bg-danger-subtle text-danger",
_ => "bg-secondary-subtle text-secondary"
};
<tr class="@(!isTaxable ? "row-nontaxable" : "")">
<td>
<a asp-controller="Invoices" asp-action="Details" asp-route-id="@inv.InvoiceId" class="text-decoration-none fw-medium">
@inv.InvoiceNumber
</a>
</td>
<td class="small">@inv.CustomerName</td>
<td class="small text-muted">@inv.InvoiceDate.ToString("MM/dd/yyyy")</td>
<td><span class="badge @statusBadge">@inv.Status</span></td>
<td class="text-end">@inv.SubTotal.ToString("C")</td>
<td class="text-end text-muted small">@(isTaxable ? inv.TaxPercent.ToString("F2") + "%" : "—")</td>
<td class="text-end @(isTaxable ? "fw-semibold text-primary" : "text-muted")">@(isTaxable ? inv.TaxAmount.ToString("C") : "—")</td>
<td class="text-end fw-semibold">@inv.Total.ToString("C")</td>
<td class="text-end text-success no-print">@inv.AmountPaid.ToString("C")</td>
<td class="small text-muted">@(string.IsNullOrEmpty(inv.TaxAccountName) ? "—" : inv.TaxAccountName)</td>
</tr>
}
</tbody>
<tfoot class="table-light fw-semibold">
<tr>
<td colspan="4">Totals</td>
<td class="text-end">@(Model.TotalTaxableSales + Model.TotalNonTaxableSales).ToString("C")</td>
<td></td>
<td class="text-end text-primary">@Model.TotalTaxBilled.ToString("C")</td>
<td class="text-end">@Model.Invoices.Sum(i => i.Total).ToString("C")</td>
<td class="text-end text-success no-print">@Model.Invoices.Sum(i => i.AmountPaid).ToString("C")</td>
<td></td>
</tr>
</tfoot>
</table>
</div>
</div>
}
<div class="text-muted small mt-2 no-print">
<i class="bi bi-info-circle me-1"></i>
Generated @DateTime.Now.ToString("MMM d, yyyy h:mm tt") · Invoice basis — tax liability is recognized when invoiced, not when collected. Excludes Draft and Voided invoices.
</div>
@if (Model.ByMonth.Count > 1)
{
@section Scripts {
<script src="~/lib/chartjs/chart.umd.min.js"></script>
<script>
(function() {
const isDark = document.documentElement.getAttribute('data-bs-theme') === 'dark';
const gridColor = isDark ? 'rgba(255,255,255,0.1)' : 'rgba(0,0,0,0.05)';
const textColor = isDark ? '#adb5bd' : '#6c757d';
new Chart(document.getElementById('taxTrendChart'), {
type: 'bar',
data: {
labels: @Html.Raw(System.Text.Json.JsonSerializer.Serialize(monthLabels)),
datasets: [
{
label: 'Taxable Sales',
data: @Html.Raw(System.Text.Json.JsonSerializer.Serialize(monthTaxable)),
backgroundColor: 'rgba(79,70,229,0.5)',
borderRadius: 4,
order: 2
},
{
label: 'Tax Billed',
data: @Html.Raw(System.Text.Json.JsonSerializer.Serialize(monthTaxBilled)),
type: 'line',
borderColor: '#3b82f6',
backgroundColor: 'rgba(59,130,246,0.1)',
borderWidth: 2,
pointRadius: 4,
fill: false,
tension: 0.3,
order: 1,
yAxisID: 'y1'
}
]
},
options: {
responsive: true,
plugins: {
legend: { display: true },
tooltip: { callbacks: { label: ctx => ' $' + ctx.parsed.y.toLocaleString('en-US', {minimumFractionDigits:2}) } }
},
scales: {
y: { ticks: { color: textColor, callback: v => '$' + v.toLocaleString() }, grid: { color: gridColor }, title: { display: true, text: 'Taxable Sales', color: textColor } },
y1: { ticks: { color: textColor, callback: v => '$' + v.toLocaleString() }, grid: { display: false }, position: 'right', title: { display: true, text: 'Tax Billed', color: textColor } },
x: { ticks: { color: textColor }, grid: { display: false } }
}
}
});
})();
</script>
}
}