379b0de885
- AccountingDropdownHelper: wired into BillsController and ExpensesController, replacing 35-40 lines of duplicated DB queries per controller - AppConstants.StatusCodes: added Job.* and Quote.* constants to replace all magic status strings across Jobs, Quotes, Appointments, OvenScheduler, AiQuickQuote, QuoteApproval, and AccountingDropdownHelper - AccountingRules: extracted IsNormalDebitBalance into shared Infrastructure helper; removed duplicate private method from AccountBalanceService and LedgerService (~50 lines deleted) - AccountDataExportController: extracted 9 Fetch*Async methods (superset of includes) so Add*Sheet and Build*Csv no longer duplicate DB queries; each entity is queried once regardless of whether XLSX or CSV format is requested - BillsController.Create and ExpensesController.Create wrapped in ExecuteInTransactionAsync; blob uploads moved after commit to keep financial data atomic and prevent orphaned blobs from rolling back - Number generators (Appointments, CreditMemo, OvenBatch) fixed from full-table GetAllAsync to prefix-filtered FindAsync Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
101 lines
4.5 KiB
C#
101 lines
4.5 KiB
C#
using Microsoft.AspNetCore.Mvc.Rendering;
|
||
using PowderCoating.Core.Enums;
|
||
using PowderCoating.Core.Interfaces;
|
||
using PowderCoating.Shared.Constants;
|
||
|
||
namespace PowderCoating.Web.Helpers;
|
||
|
||
/// <summary>
|
||
/// Centralizes the repeated DB queries and SelectListItem projections used by the accounting
|
||
/// controllers (Bills, Expenses). Each controller assigns only the properties it needs to ViewBag,
|
||
/// so the naming mismatch between controllers (BankAccounts vs PaymentAccounts) is harmless.
|
||
/// </summary>
|
||
internal static class AccountingDropdownHelper
|
||
{
|
||
/// <summary>
|
||
/// Loads vendors, accounts, payment methods, and active jobs in a single call.
|
||
/// Returns pre-projected SelectListItem collections so controllers avoid duplicating the
|
||
/// LINQ-to-SelectListItem transform.
|
||
/// </summary>
|
||
internal static async Task<AccountingDropdowns> LoadAsync(IUnitOfWork unitOfWork)
|
||
{
|
||
var vendors = await unitOfWork.Vendors.FindAsync(v => v.IsActive);
|
||
var allAccounts = await unitOfWork.Accounts.FindAsync(a => a.IsActive);
|
||
var jobs = await unitOfWork.Jobs.FindAsync(j =>
|
||
j.JobStatus.StatusCode != AppConstants.StatusCodes.Job.Completed &&
|
||
j.JobStatus.StatusCode != AppConstants.StatusCodes.Job.Cancelled &&
|
||
j.JobStatus.StatusCode != AppConstants.StatusCodes.Job.Delivered);
|
||
|
||
var accountLabel = (Core.Entities.Account a) => $"{a.AccountNumber} – {a.Name}";
|
||
|
||
return new AccountingDropdowns
|
||
{
|
||
Vendors = vendors
|
||
.OrderBy(v => v.CompanyName)
|
||
.Select(v => new SelectListItem(v.CompanyName, v.Id.ToString()))
|
||
.ToList(),
|
||
|
||
ExpenseAccounts = allAccounts
|
||
.Where(a => a.AccountType == AccountType.Expense ||
|
||
a.AccountType == AccountType.CostOfGoods)
|
||
.OrderBy(a => a.AccountNumber)
|
||
.Select(a => new SelectListItem(accountLabel(a), a.Id.ToString()))
|
||
.ToList(),
|
||
|
||
ExpenseAndAssetAccounts = allAccounts
|
||
.Where(a => a.AccountType == AccountType.Expense ||
|
||
a.AccountType == AccountType.CostOfGoods ||
|
||
a.AccountType == AccountType.Asset)
|
||
.OrderBy(a => a.AccountNumber)
|
||
.Select(a => new SelectListItem(accountLabel(a), a.Id.ToString()))
|
||
.ToList(),
|
||
|
||
ApAccounts = allAccounts
|
||
.Where(a => a.AccountSubType == AccountSubType.AccountsPayable)
|
||
.OrderBy(a => a.AccountNumber)
|
||
.Select(a => new SelectListItem(accountLabel(a), a.Id.ToString()))
|
||
.ToList(),
|
||
|
||
BankAccounts = allAccounts
|
||
.Where(a => a.AccountSubType == AccountSubType.Cash ||
|
||
a.AccountSubType == AccountSubType.Checking ||
|
||
a.AccountSubType == AccountSubType.Savings ||
|
||
a.AccountSubType == AccountSubType.CreditCard)
|
||
.OrderBy(a => a.AccountNumber)
|
||
.Select(a => new SelectListItem(accountLabel(a), a.Id.ToString()))
|
||
.ToList(),
|
||
|
||
PaymentMethods = Enum.GetValues<PaymentMethod>()
|
||
.Select(m => new SelectListItem(m.ToString(), ((int)m).ToString()))
|
||
.ToList(),
|
||
|
||
ActiveJobs = jobs
|
||
.OrderBy(j => j.JobNumber)
|
||
.Select(j => new SelectListItem(
|
||
$"{j.JobNumber} – {j.Description ?? "No description"}",
|
||
j.Id.ToString()))
|
||
.ToList()
|
||
};
|
||
}
|
||
}
|
||
|
||
internal sealed class AccountingDropdowns
|
||
{
|
||
public IReadOnlyList<SelectListItem> Vendors { get; init; } = [];
|
||
|
||
/// <summary>Expense + Cost of Goods accounts (used by Expenses controller).</summary>
|
||
public IReadOnlyList<SelectListItem> ExpenseAccounts { get; init; } = [];
|
||
|
||
/// <summary>Expense + Cost of Goods + Asset accounts (used by Bills controller).</summary>
|
||
public IReadOnlyList<SelectListItem> ExpenseAndAssetAccounts { get; init; } = [];
|
||
|
||
/// <summary>Accounts Payable accounts (used by Bills controller).</summary>
|
||
public IReadOnlyList<SelectListItem> ApAccounts { get; init; } = [];
|
||
|
||
/// <summary>Cash, Checking, Savings, and Credit Card accounts.</summary>
|
||
public IReadOnlyList<SelectListItem> BankAccounts { get; init; } = [];
|
||
|
||
public IReadOnlyList<SelectListItem> PaymentMethods { get; init; } = [];
|
||
public IReadOnlyList<SelectListItem> ActiveJobs { get; init; } = [];
|
||
}
|