Fix 4 post-review issues found in accounting module audit

- Drop orphan VendorCreditId1 column from VendorCreditApplications (was
  scaffolded by EF because WithMany() lacked inverse navigation name;
  fixed WithMany() → WithMany(vc => vc.Applications) in ApplicationDbContext)
- Wire EarlyPaymentDiscount fields through full data path: added
  EarlyPaymentDiscountPercent/Days to CreateInvoiceDto, hidden inputs to
  Invoice Create view, and JS to populate from customer AJAX response
- Add missing [HttpGet] attribute to TaxRatesController.Index
- Document GenerateNow architecture exception with XML rationale

Migration DropOrphanVendorCreditId1 applied. Build: 0 errors, 168 warnings.
Unit tests: 200/200 passing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-10 11:32:44 -04:00
parent 14026818e2
commit d94612cc9c
8 changed files with 10296 additions and 15 deletions
@@ -174,6 +174,15 @@ public class RecurringTemplatesController : Controller
/// Forces immediate generation of the next occurrence and advances NextFireDate,
/// regardless of the scheduled date. Useful for testing a new template or catching up
/// after a configuration change.
/// <para>
/// INTENTIONAL EXCEPTION to the no-DbContext-in-controllers rule: this action must
/// execute the same multi-step entity creation that <see cref="RecurringTransactionService"/>
/// does in a background scope. Creating an inner DI scope here avoids duplicating that
/// service's interface (which would require exposing a public synchronous Fire method on a
/// singleton BackgroundService, which is unsafe). The scope is disposed after the action
/// completes, so no DbContext leak occurs. This pattern mirrors how BackgroundService
/// itself resolves scoped services.
/// </para>
/// </summary>
[HttpPost]
[ValidateAntiForgeryToken]
@@ -184,6 +193,8 @@ public class RecurringTemplatesController : Controller
try
{
// Intentional: create a fresh DI scope so the inner DbContext is isolated from the
// request's IUnitOfWork context. See summary above for rationale.
using var scope = HttpContext.RequestServices.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<PowderCoating.Infrastructure.Data.ApplicationDbContext>();
@@ -29,6 +29,7 @@ public class TaxRatesController : Controller
}
/// <summary>Lists all tax rates for the current company.</summary>
[HttpGet]
public async Task<IActionResult> Index()
{
var rates = await _unitOfWork.TaxRates.GetAllAsync();