diff --git a/src/PowderCoating.Web/Controllers/JournalEntriesController.cs b/src/PowderCoating.Web/Controllers/JournalEntriesController.cs index 19795d3..1446119 100644 --- a/src/PowderCoating.Web/Controllers/JournalEntriesController.cs +++ b/src/PowderCoating.Web/Controllers/JournalEntriesController.cs @@ -196,6 +196,105 @@ public class JournalEntriesController : Controller return RedirectToAction(nameof(Details), new { id }); } + // ── Sales Tax Remittance ─────────────────────────────────────────────────── + + /// + /// Form to record a sales tax payment to the tax authority. Shows the current Sales Tax Payable + /// (2200) liability and a bank-account picker. Relieves the liability that invoices accumulate. + /// + // GET: /JournalEntries/SalesTaxPayment + public async Task SalesTaxPayment() + { + if (!AllowAccounting()) return RedirectToAction("Landing", "Reports"); + var companyId = _tenantContext.GetCurrentCompanyId() ?? 0; + + var taxAcct = (await _unitOfWork.Accounts.FindAsync( + a => a.CompanyId == companyId && a.AccountNumber == "2200" && a.IsActive)).FirstOrDefault(); + ViewBag.TaxLiability = taxAcct?.CurrentBalance ?? 0m; + ViewBag.TaxAccountFound = taxAcct != null; + + var banks = await _unitOfWork.Accounts.FindAsync(a => a.CompanyId == companyId && a.IsActive + && (a.AccountSubType == AccountSubType.Checking + || a.AccountSubType == AccountSubType.Savings + || a.AccountSubType == AccountSubType.Cash)); + ViewBag.BankAccounts = banks.OrderBy(a => a.AccountNumber) + .Select(a => new SelectListItem($"{a.AccountNumber} – {a.Name}", a.Id.ToString())).ToList(); + + return View(); + } + + /// + /// Records a sales tax remittance as a posted journal entry: DR Sales Tax Payable (2200) / CR the + /// chosen bank account. Honors the period lock. The reporting already accounts for posted JE lines, + /// so this is all that's needed to relieve the liability. + /// + // POST: /JournalEntries/SalesTaxPayment + [HttpPost] + [Authorize(Policy = AppConstants.Policies.CanManageJobs)] + [ValidateAntiForgeryToken] + public async Task SalesTaxPayment(decimal amount, DateTime paymentDate, int bankAccountId, string? reference) + { + if (!AllowAccounting()) return RedirectToAction("Landing", "Reports"); + var companyId = _tenantContext.GetCurrentCompanyId() ?? 0; + + if (amount <= 0) + { + TempData["Error"] = "Enter a payment amount greater than zero."; + return RedirectToAction(nameof(SalesTaxPayment)); + } + + var taxAcct = (await _unitOfWork.Accounts.FindAsync( + a => a.CompanyId == companyId && a.AccountNumber == "2200" && a.IsActive)).FirstOrDefault(); + if (taxAcct == null) + { + TempData["Error"] = "No active Sales Tax Payable (2200) account found in your chart of accounts."; + return RedirectToAction(nameof(SalesTaxPayment)); + } + + var bankAcct = await _unitOfWork.Accounts.GetByIdAsync(bankAccountId); + if (bankAcct == null || bankAcct.CompanyId != companyId) + { + TempData["Error"] = "Select a valid bank account to pay from."; + return RedirectToAction(nameof(SalesTaxPayment)); + } + + var company = await _unitOfWork.Companies.GetByIdAsync(companyId); + if (Web.Helpers.AccountingPeriodValidator.IsLocked(paymentDate, company?.BookLockedThrough)) + { + TempData["Error"] = Web.Helpers.AccountingPeriodValidator.LockedMessage(company!.BookLockedThrough); + return RedirectToAction(nameof(SalesTaxPayment)); + } + + var entryNumber = await GenerateEntryNumberAsync(companyId); + var entry = new JournalEntry + { + EntryNumber = entryNumber, + EntryDate = paymentDate, + Reference = string.IsNullOrWhiteSpace(reference) ? "Sales tax remittance" : reference.Trim(), + Description = $"Sales tax remittance — {amount:C}", + Status = JournalEntryStatus.Posted, + PostedAt = DateTime.UtcNow, + PostedBy = User.Identity?.Name, + CompanyId = companyId, + Lines = new List + { + new() { AccountId = taxAcct.Id, DebitAmount = amount, CreditAmount = 0, Description = "Sales tax paid to authority", LineOrder = 0, CompanyId = companyId }, + new() { AccountId = bankAcct.Id, DebitAmount = 0, CreditAmount = amount, Description = $"Paid from {bankAcct.Name}", LineOrder = 1, CompanyId = companyId } + } + }; + + await _unitOfWork.ExecuteInTransactionAsync(async () => + { + await _unitOfWork.JournalEntries.AddAsync(entry); + await _accountBalanceService.DebitAsync(taxAcct.Id, amount); // reduce the liability + await _accountBalanceService.CreditAsync(bankAcct.Id, amount); // cash leaves the bank + await _unitOfWork.CompleteAsync(); + }); + + TempData["Success"] = $"Recorded sales tax remittance of {amount:C} ({entryNumber})."; + return RedirectToAction(nameof(Details), new { id = entry.Id }); + } + // ── Reverse ────────────────────────────────────────────────────────────── [HttpPost] diff --git a/src/PowderCoating.Web/Views/JournalEntries/SalesTaxPayment.cshtml b/src/PowderCoating.Web/Views/JournalEntries/SalesTaxPayment.cshtml new file mode 100644 index 0000000..fdb4113 --- /dev/null +++ b/src/PowderCoating.Web/Views/JournalEntries/SalesTaxPayment.cshtml @@ -0,0 +1,72 @@ +@{ + ViewData["Title"] = "Record Sales Tax Payment"; + ViewData["PageIcon"] = "bi-cash-stack"; + var taxLiability = (decimal)(ViewBag.TaxLiability ?? 0m); + var taxFound = (bool)(ViewBag.TaxAccountFound ?? false); + var banks = ViewBag.BankAccounts as List ?? new List(); +} + + + + Record Sales Tax Payment + + +@if (!taxFound) +{ + + No active Sales Tax Payable (2200) account was found in your chart of accounts. Add one first. + +} +else +{ + + + + + + Current Sales Tax Payable + Tax collected on invoices and owed to the authority. + + @taxLiability.ToString("C") + + + + + @Html.AntiForgeryToken() + + + Amount paid + + $ + + + Defaults to the full balance — edit if you're paying a partial period. + + + Payment date + + + + Paid from (bank account) + + Select an account… + @foreach (var b in banks) + { + @b.Text + } + + + + Reference / period (optional) + + + + Posts a journal entry: DR Sales Tax Payable / CR the chosen bank account. + + Record payment + + + + +} diff --git a/src/PowderCoating.Web/Views/Reports/SalesTax.cshtml b/src/PowderCoating.Web/Views/Reports/SalesTax.cshtml index 4d33622..9394381 100644 --- a/src/PowderCoating.Web/Views/Reports/SalesTax.cshtml +++ b/src/PowderCoating.Web/Views/Reports/SalesTax.cshtml @@ -31,6 +31,9 @@ @Model.From.ToString("MMM d") – @Model.To.ToString("MMM d, yyyy") · @(Model.TaxableInvoiceCount + Model.NonTaxableInvoiceCount) invoices + + Record Payment + Export CSV
@Model.From.ToString("MMM d") – @Model.To.ToString("MMM d, yyyy") · @(Model.TaxableInvoiceCount + Model.NonTaxableInvoiceCount) invoices