a0bdd2b5b4
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>
192 lines
9.3 KiB
Plaintext
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 — 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 — @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>
|
|
}
|