Files
PowderCoatingLogix/src/PowderCoating.Web/Views/Expenses/Details.cshtml
T
spouliot a0bdd2b5b4 Sweep all .cshtml files for encoding corruption; add pre-commit guard
Replace all corruption variants with HTML entities across 226 view files:
- 3-char UTF-8-as-Win1252 sequences (ae-corruption)
- Standalone smart/curly quotes that break C# Razor expressions
- Partially re-corrupted variants where the 3rd byte was normalised to ASCII

tools/Fix-Encoding.ps1: re-runnable sweep; uses [char] code points so the
script itself never contains a literal non-ASCII character; supports -DryRun

.githooks/pre-commit: blocks commits containing the ae-corruption byte
signature (xc3xa2xe2x82xac); git core.hooksPath = .githooks so the
hook is repo-committed and active for all future work on this machine.

Build clean; 225 unit tests pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 21:37:10 -04:00

192 lines
9.3 KiB
Plaintext

@model PowderCoating.Application.DTOs.Accounting.ExpenseDto
@{
ViewData["Title"] = $"Expense {Model.ExpenseNumber}";
ViewData["PageIcon"] = "bi-receipt";
ViewData["PageHelpTitle"] = "Expense";
ViewData["PageHelpContent"] = "A direct purchase paid at the time of transaction. Expense Account shows what was bought; Paid From shows which bank/cash account was debited. Edit to correct any details. Delete permanently removes the record &mdash; there is no Void for expenses.";
}
<div class="d-flex justify-content-between align-items-center mb-4">
<div class="d-flex align-items-center gap-2">
<a asp-action="Index" class="btn btn-sm btn-outline-secondary"><i class="bi bi-arrow-left"></i></a>
<span class="text-muted small">@Model.Date.ToString("MMMM d, yyyy")</span>
</div>
<div class="d-flex gap-2">
<a asp-action="Edit" asp-route-id="@Model.Id" class="btn btn-outline-secondary">
<i class="bi bi-pencil me-1"></i>Edit
</a>
<form asp-action="Delete" asp-route-id="@Model.Id" method="post"
onsubmit="return confirm('Delete expense @Model.ExpenseNumber?')">
@Html.AntiForgeryToken()
<button type="submit" class="btn btn-outline-danger">
<i class="bi bi-trash me-1"></i>Delete
</button>
</form>
</div>
</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-7">
<div class="card shadow-sm">
<div class="card-header fw-semibold d-flex align-items-center gap-2">
Expense Details
<a tabindex="0" class="help-icon" role="button"
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
data-bs-title="Expense Details"
data-bs-content="Expense Account: the category debited (what was purchased). Paid From: the bank or cash account that was credited. Payment Method: how it was paid. If a Job is linked, this expense contributes to that job's cost tracking.">
<i class="bi bi-question-circle"></i>
</a>
</div>
<div class="card-body">
<dl class="row mb-0">
<dt class="col-sm-4 text-muted">Date</dt>
<dd class="col-sm-8">@Model.Date.ToString("MMMM d, yyyy")</dd>
<dt class="col-sm-4 text-muted">Expense Account</dt>
<dd class="col-sm-8">
<code>@Model.ExpenseAccountNumber</code> @Model.ExpenseAccountName
</dd>
<dt class="col-sm-4 text-muted">Paid From</dt>
<dd class="col-sm-8">@Model.PaymentAccountName</dd>
<dt class="col-sm-4 text-muted">Payment Method</dt>
<dd class="col-sm-8">@Model.PaymentMethod</dd>
@if (!string.IsNullOrEmpty(Model.VendorName))
{
<dt class="col-sm-4 text-muted">Vendor</dt>
<dd class="col-sm-8">@Model.VendorName</dd>
}
@if (!string.IsNullOrEmpty(Model.JobNumber))
{
<dt class="col-sm-4 text-muted">Job</dt>
<dd class="col-sm-8">@Model.JobNumber</dd>
}
@if (!string.IsNullOrEmpty(Model.Memo))
{
<dt class="col-sm-4 text-muted">Memo</dt>
<dd class="col-sm-8">@Model.Memo</dd>
}
<dt class="col-sm-4 text-muted">Recorded</dt>
<dd class="col-sm-8 text-muted small">@Model.CreatedAt.ToString("MMM d, yyyy h:mm tt")</dd>
</dl>
</div>
</div>
</div>
<div class="col-lg-5">
<div class="card shadow-sm border-primary mb-3">
<div class="card-body text-center py-4">
<p class="text-muted mb-1 small">Amount</p>
<p class="display-5 fw-bold text-primary mb-0">@Model.Amount.ToString("C")</p>
</div>
</div>
<!-- Receipt -->
<div class="card shadow-sm">
<div class="card-header fw-semibold d-flex align-items-center justify-content-between">
<div class="d-flex align-items-center gap-2">
<span><i class="bi bi-receipt me-1"></i>Receipt</span>
<a tabindex="0" class="help-icon" role="button"
data-bs-toggle="popover" data-bs-placement="left" data-bs-trigger="focus"
data-bs-title="Receipt"
data-bs-content="Attached receipt image or PDF for this expense. Click to view full size. Use the trash icon to remove it. To replace the receipt, use Edit and upload a new file.">
<i class="bi bi-question-circle"></i>
</a>
</div>
@if (!string.IsNullOrEmpty(Model.ReceiptFilePath))
{
<form asp-action="DeleteReceipt" asp-route-id="@Model.Id" method="post"
onsubmit="return confirm('Remove receipt?')">
@Html.AntiForgeryToken()
<button type="submit" class="btn btn-sm btn-outline-danger">
<i class="bi bi-trash"></i>
</button>
</form>
}
</div>
<div class="card-body">
@if (!string.IsNullOrEmpty(Model.ReceiptFilePath))
{
var ext = System.IO.Path.GetExtension(Model.ReceiptFilePath).ToLowerInvariant();
if (ext == ".pdf")
{
<div class="text-center py-3">
<i class="bi bi-file-earmark-pdf text-danger" style="font-size:3rem;"></i>
<p class="small text-muted mt-2 mb-3">PDF receipt attached</p>
<a asp-action="ViewReceipt" asp-route-id="@Model.Id"
class="btn btn-outline-primary btn-sm">
<i class="bi bi-download me-1"></i>Download PDF
</a>
</div>
}
else
{
<a href="#" data-bs-toggle="modal" data-bs-target="#receiptModal" style="cursor:zoom-in;">
<img src="@Url.Action("ViewReceipt", new { id = Model.Id })"
alt="Receipt" class="img-fluid rounded" style="max-height:300px;object-fit:contain;" />
</a>
<div class="text-center mt-2">
<button type="button" class="btn btn-outline-secondary btn-sm"
data-bs-toggle="modal" data-bs-target="#receiptModal">
<i class="bi bi-arrows-fullscreen me-1"></i>View Full Size
</button>
</div>
}
}
else
{
<div class="text-center py-3 text-muted">
<i class="bi bi-image fs-2 d-block mb-2"></i>
<p class="small mb-2">No receipt attached</p>
<a asp-action="Edit" asp-route-id="@Model.Id" class="btn btn-outline-secondary btn-sm">
<i class="bi bi-upload me-1"></i>Upload Receipt
</a>
</div>
}
</div>
</div>
</div>
</div>
@if (!string.IsNullOrEmpty(Model.ReceiptFilePath) &&
System.IO.Path.GetExtension(Model.ReceiptFilePath).ToLowerInvariant() != ".pdf")
{
<div class="modal fade" id="receiptModal" tabindex="-1" aria-labelledby="receiptModalLabel" aria-hidden="true">
<div class="modal-dialog modal-xl modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="receiptModalLabel">
<i class="bi bi-receipt me-2"></i>Receipt &mdash; @Model.ExpenseNumber
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body text-center p-2">
<img src="@Url.Action("ViewReceipt", new { id = Model.Id })"
alt="Receipt" class="img-fluid" style="max-height:80vh;object-fit:contain;" />
</div>
<div class="modal-footer">
<a asp-action="ViewReceipt" asp-route-id="@Model.Id" target="_blank"
class="btn btn-outline-secondary btn-sm">
<i class="bi bi-box-arrow-up-right me-1"></i>Open in New Tab
</a>
<button type="button" class="btn btn-outline-secondary btn-sm" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
}