Phase F: Customer/Vendor Statements, Payment Terms Parser, Tax Rates
F1: GetCustomerStatementAsync/GetVendorStatementAsync on IFinancialReportService;
StatementLineDto; CustomerStatementDto/VendorStatementDto; Statement action on
CustomersController + VendorsController; Statement views + PDF download via
StatementPdfHelper (QuestPDF); Statement button on Customer/Vendor Details pages.
F2: PaymentTermsParser static helper (CalculateDueDate, ParseEarlyPaymentDiscount);
EarlyPaymentDiscountPercent/Days on Invoice entity; GetCustomerPaymentTerms AJAX
endpoint on InvoicesController auto-populates Terms + due date on customer select;
early payment discount notice on Invoice Create.
F3: TaxRate entity (Name/Rate/State/IsDefault/IsActive, tenant-filtered);
IUnitOfWork.TaxRates + UnitOfWork + ApplicationDbContext; TaxRatesController
(Index/Create/Edit/Delete/ToggleActive, CompanyAdminOnly); GetTaxRateForCustomer
AJAX endpoint; Tax Rates in Settings gear menu.
Also fixes AddVendorCredits migration: VendorCreditApplications FKs changed from
CASCADE to NoAction to resolve SQL Server error 1785 (multiple cascade paths).
Migration: AddPaymentTermsAndTaxRates applied locally; 200/200 unit tests pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
@model PowderCoating.Application.DTOs.Accounting.VendorStatementDto
|
||||
@{
|
||||
ViewData["Title"] = $"Statement – {Model.VendorName}";
|
||||
}
|
||||
|
||||
<div class="d-flex justify-content-between align-items-start mb-4 flex-wrap gap-2">
|
||||
<div>
|
||||
<h4 class="mb-0">Vendor Statement</h4>
|
||||
<p class="text-muted mb-0">@Model.VendorName · @Model.From.ToString("MMM d, yyyy") – @Model.To.ToString("MMM d, yyyy")</p>
|
||||
</div>
|
||||
<div class="d-flex gap-2 flex-wrap">
|
||||
<form method="get" class="d-flex gap-2 align-items-center">
|
||||
<input type="hidden" name="id" value="@(ViewContext.RouteData.Values["id"])" />
|
||||
<input type="date" name="from" class="form-control form-control-sm" value="@Model.From.ToString("yyyy-MM-dd")" />
|
||||
<input type="date" name="to" class="form-control form-control-sm" value="@Model.To.ToString("yyyy-MM-dd")" />
|
||||
<button type="submit" class="btn btn-sm btn-outline-secondary">Refresh</button>
|
||||
</form>
|
||||
<a asp-action="Statement" asp-route-id="@(ViewContext.RouteData.Values["id"])"
|
||||
asp-route-from="@Model.From.ToString("yyyy-MM-dd")"
|
||||
asp-route-to="@Model.To.ToString("yyyy-MM-dd")"
|
||||
asp-route-pdf="true"
|
||||
class="btn btn-sm btn-outline-primary">
|
||||
<i class="bi bi-file-earmark-pdf me-1"></i>Download PDF
|
||||
</a>
|
||||
<a asp-action="Details" asp-route-id="@(ViewContext.RouteData.Values["id"])" class="btn btn-sm btn-outline-secondary">
|
||||
<i class="bi bi-arrow-left me-1"></i>Back
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-header border-0 py-3 bg-white d-flex justify-content-between align-items-center">
|
||||
<span class="fw-semibold">@Model.VendorName</span>
|
||||
<div class="text-muted small">@Model.CompanyName</div>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<table class="table table-sm mb-0">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
<th style="width:100px">Date</th>
|
||||
<th style="width:120px">Type</th>
|
||||
<th style="width:130px">Reference</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>
|
||||
<tr class="table-light fw-semibold">
|
||||
<td class="text-muted">@Model.From.AddDays(-1).ToString("MM/dd/yy")</td>
|
||||
<td colspan="5">Opening Balance</td>
|
||||
<td class="text-end">@Model.OpeningBalance.ToString("C")</td>
|
||||
</tr>
|
||||
|
||||
@if (!Model.Lines.Any())
|
||||
{
|
||||
<tr>
|
||||
<td colspan="7" class="text-center text-muted py-4">No activity in this period.</td>
|
||||
</tr>
|
||||
}
|
||||
else
|
||||
{
|
||||
@foreach (var line in Model.Lines)
|
||||
{
|
||||
<tr>
|
||||
<td class="text-muted small">@line.Date.ToString("MM/dd/yy")</td>
|
||||
<td>
|
||||
<span class="badge @(line.Type == "Bill" ? "bg-danger" : line.Type == "Payment" ? "bg-success" : "bg-secondary") text-white">
|
||||
@line.Type
|
||||
</span>
|
||||
</td>
|
||||
<td class="small">@line.Reference</td>
|
||||
<td class="small text-muted">@line.Description</td>
|
||||
<td class="text-end small">@(line.Debit.HasValue ? line.Debit.Value.ToString("C") : "")</td>
|
||||
<td class="text-end small">@(line.Credit.HasValue ? line.Credit.Value.ToString("C") : "")</td>
|
||||
<td class="text-end small @(line.RunningBalance > 0 ? "text-danger" : "text-success") fw-semibold">
|
||||
@line.RunningBalance.ToString("C")
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
}
|
||||
|
||||
<tr class="table-secondary fw-bold">
|
||||
<td colspan="6">Closing Balance</td>
|
||||
<td class="text-end @(Model.ClosingBalance > 0 ? "text-danger" : "text-success")">
|
||||
@Model.ClosingBalance.ToString("C")
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user