From 3ad6b0d08f82e4461f0b7ff049d8e9f98615ce3b Mon Sep 17 00:00:00 2001 From: Scott Pouliot Date: Wed, 20 May 2026 13:27:10 -0400 Subject: [PATCH] Fix voided invoice leaving deposits locked as applied When an invoice was voided, deposits auto-applied at invoice creation kept their AppliedToInvoiceId pointing at the voided invoice. The replacement invoice lookup (AppliedToInvoiceId == null) skipped them, so the deposit was never re-applied and the customer was charged in full. Void now clears AppliedToInvoiceId/AppliedDate on all deposits tied to the invoice so they're available for the next invoice, and credits the CustomerDeposits 2300 liability account to restore the balance that was debited when the deposits were originally applied. Co-Authored-By: Claude Sonnet 4.6 --- .../Controllers/InvoicesController.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/PowderCoating.Web/Controllers/InvoicesController.cs b/src/PowderCoating.Web/Controllers/InvoicesController.cs index ddb3537..dc1233d 100644 --- a/src/PowderCoating.Web/Controllers/InvoicesController.cs +++ b/src/PowderCoating.Web/Controllers/InvoicesController.cs @@ -1480,6 +1480,29 @@ public class InvoicesController : Controller await _unitOfWork.Payments.SoftDeleteAsync(payment.Id); } + // Re-release any deposits that were applied to this invoice so they can be + // auto-applied to the replacement invoice. Without this, AppliedToInvoiceId + // stays set and the deposit lookup (AppliedToInvoiceId == null) skips them. + var appliedDeposits = await _unitOfWork.Deposits.FindAsync( + d => d.AppliedToInvoiceId == invoice.Id && !d.IsDeleted); + var totalDepositReleased = 0m; + foreach (var deposit in appliedDeposits) + { + deposit.AppliedToInvoiceId = null; + deposit.AppliedDate = null; + deposit.UpdatedAt = DateTime.UtcNow; + await _unitOfWork.Deposits.UpdateAsync(deposit); + totalDepositReleased += deposit.Amount; + } + // Restore the CustomerDeposits 2300 liability that was cleared when the deposits + // were applied. Mirrors the DR at apply time; follows the same simplified reversal + // pattern as the rest of the void (regular payment GL entries are also left as-is). + if (totalDepositReleased > 0) + { + var custDepositsAcctId = await GetCustomerDepositsAccountIdAsync(invoice.CompanyId); + await _accountBalanceService.CreditAsync(custDepositsAcctId, totalDepositReleased); + } + // Void any gift certificates that were generated from this invoice. // Capture each GC's remaining balance BEFORE voiding so the GL entries below can use it. var gcLiabilityAcctId = await GetGcLiabilityAccountIdAsync(invoice.CompanyId);