Close all GL entry gaps across the accounting surface
- Stripe payments/refunds/chargebacks now post DR/CR entries (PaymentController) - Vendor credit void now reverses the posted GL lines (VendorCreditsController) - Gift certificate issue/redeem/void post GL to account 2500 GC Liability; FinancialReportService Trial Balance + Balance Sheet include GC liability and breakage income; P&L shows deferred revenue deduction and breakage income line - Customer deposits now post DR Checking / CR 2300 on record, reverse on delete; invoice auto-apply uses DR 2300 / CR AR (not a second bank debit); draft invoice delete reverses deposit-apply GL before the AR reversal - Deposit.DepositAccountId column added; account 2300 seeded via migration - InvoicesController.ApplyCredit now posts DR Sales Discounts / CR AR, consistent with CreditMemosController.Apply - IssueRefund (cash/card) posts DR AR / CR Bank and sets Refund.DepositAccountId; refund modal gains a bank account selector hidden for store-credit path - CancelRefund (cash/card) reverses the IssueRefund GL entries - LedgerService GetAccountLedgerAsync + ComputePriorBalanceAsync now include Refunds, CreditMemoApplications, VendorCreditApplications, GC Liability (2500), and Customer Deposits (2300) so account ledger view and RecalculateAllAsync produce correct balances - Three EF migrations applied: SeedSalesDiscountsAccount, AccountingGapsPhase2, AccountingDepositsGL - Unit tests updated for new IAccountBalanceService constructor params (200/200) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -195,6 +195,10 @@ public class VendorCreditsController : Controller
|
||||
foreach (var line in vc.LineItems)
|
||||
await _accountBalanceService.CreditAsync(line.AccountId, line.Amount);
|
||||
|
||||
// Record posting date so Void() can reverse only if GL entries were actually made.
|
||||
vc.PostedDate = DateTime.UtcNow;
|
||||
await _unitOfWork.VendorCredits.UpdateAsync(vc);
|
||||
|
||||
// Status stays Open — the credit is now in the GL but not yet applied to a bill
|
||||
await _unitOfWork.CompleteAsync();
|
||||
});
|
||||
@@ -260,6 +264,12 @@ public class VendorCreditsController : Controller
|
||||
|
||||
// ── Void ─────────────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Voids a vendor credit. If the credit was previously posted (PostedDate is set), reverses the
|
||||
/// original GL entries: CR Accounts Payable / DR each expense line item, restoring both balances.
|
||||
/// Only the unapplied RemainingAmount of AP is reversed — applied portions reduced bill balances
|
||||
/// that are already settled and remain part of the immutable audit trail.
|
||||
/// </summary>
|
||||
[HttpPost]
|
||||
[Authorize(Policy = AppConstants.Policies.CompanyAdminOnly)]
|
||||
[ValidateAntiForgeryToken]
|
||||
@@ -267,7 +277,10 @@ public class VendorCreditsController : Controller
|
||||
{
|
||||
if (!AllowAccounting()) return RedirectToAction("Landing", "Reports");
|
||||
|
||||
var vc = await _unitOfWork.VendorCredits.GetByIdAsync(id);
|
||||
var vc = (await _unitOfWork.VendorCredits.FindAsync(
|
||||
v => v.Id == id, false,
|
||||
v => v.LineItems))
|
||||
.FirstOrDefault();
|
||||
if (vc == null) return NotFound();
|
||||
|
||||
if (vc.Status == VendorCreditStatus.Applied)
|
||||
@@ -276,9 +289,25 @@ public class VendorCreditsController : Controller
|
||||
return RedirectToAction(nameof(Details), new { id });
|
||||
}
|
||||
|
||||
vc.Status = VendorCreditStatus.Voided;
|
||||
vc.RemainingAmount = 0;
|
||||
await _unitOfWork.CompleteAsync();
|
||||
await _unitOfWork.ExecuteInTransactionAsync(async () =>
|
||||
{
|
||||
// Reverse GL only if Post() was previously called; unposted credits have no GL entries.
|
||||
if (vc.PostedDate.HasValue && vc.RemainingAmount > 0)
|
||||
{
|
||||
// CR AP for the unapplied amount (undoes the debit made at Post time)
|
||||
await _accountBalanceService.CreditAsync(vc.APAccountId, vc.RemainingAmount);
|
||||
|
||||
// DR each expense line proportionally (unapplied fraction of each line)
|
||||
var applyRatio = vc.Total > 0 ? vc.RemainingAmount / vc.Total : 1m;
|
||||
foreach (var line in vc.LineItems)
|
||||
await _accountBalanceService.DebitAsync(line.AccountId, line.Amount * applyRatio);
|
||||
}
|
||||
|
||||
vc.Status = VendorCreditStatus.Voided;
|
||||
vc.RemainingAmount = 0;
|
||||
await _unitOfWork.VendorCredits.UpdateAsync(vc);
|
||||
await _unitOfWork.CompleteAsync();
|
||||
});
|
||||
|
||||
TempData["Success"] = $"Vendor credit {vc.CreditNumber} voided.";
|
||||
return RedirectToAction(nameof(Index));
|
||||
|
||||
Reference in New Issue
Block a user