Refactor: extract shared helpers, fix field drift, add assembly services
- IJobItemAssemblyService / IQuotePricingAssemblyService: centralize job item and quote pricing construction that was duplicated across create, rework copy, and quote-to-job conversion paths - BlobFileHelper: single ValidateUpload/GetContentType/SanitizeFileName used by 6 blob services (JobPhoto, QuotePhoto, ProfilePhoto, CompanyLogo, Equipment, Catalog) and BillsController + ExpensesController, removing 8 private copies - PagedResult<T>.From(): static factory eliminates 6-line boilerplate in 11 controllers (Appointments, Customers, Equipment, Inventory, Invoices, Jobs, Maintenance, CompanyUsers, PlatformUsers, Quotes, Vendors) - AccountingDropdownHelper: single LoadAsync() call replaces duplicate vendor/account/job queries in BillsController and ExpensesController - JobTemplateItem: add IsSalesItem + Sku fields with migration; propagate through JobTemplatesController snapshot copy and GetTemplatesJson projection, and JobsController template-application path - Test assertions updated for standardized BlobFileHelper error messages Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using PowderCoating.Core.Enums;
|
||||
using PowderCoating.Core.Interfaces;
|
||||
|
||||
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 != "COMPLETED" &&
|
||||
j.JobStatus.StatusCode != "CANCELLED" &&
|
||||
j.JobStatus.StatusCode != "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; } = [];
|
||||
}
|
||||
Reference in New Issue
Block a user