Refactor: centralize accounting helpers, status constants, and query deduplication

- 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>
This commit is contained in:
2026-05-09 22:42:39 -04:00
parent edd7389d7d
commit 379b0de885
15 changed files with 394 additions and 359 deletions
@@ -46,7 +46,7 @@ public class AccountBalanceService : IAccountBalanceService
// Debit increases debit-normal accounts (Assets/Expenses/COGS)
// Debit decreases credit-normal accounts (Liabilities/Equity/Revenue)
account.CurrentBalance += IsNormalDebitBalance(account.AccountSubType) ? amount : -amount;
account.CurrentBalance += AccountingRules.IsNormalDebitBalance(account.AccountSubType) ? amount : -amount;
await _unitOfWork.Accounts.UpdateAsync(account);
}
@@ -65,7 +65,7 @@ public class AccountBalanceService : IAccountBalanceService
// Credit decreases debit-normal accounts (Assets/Expenses/COGS)
// Credit increases credit-normal accounts (Liabilities/Equity/Revenue)
account.CurrentBalance += IsNormalDebitBalance(account.AccountSubType) ? -amount : amount;
account.CurrentBalance += AccountingRules.IsNormalDebitBalance(account.AccountSubType) ? -amount : amount;
await _unitOfWork.Accounts.UpdateAsync(account);
}
@@ -109,28 +109,4 @@ public class AccountBalanceService : IAccountBalanceService
await _unitOfWork.CompleteAsync();
}
/// <summary>
/// Returns <c>true</c> for account sub-types whose normal balance is a debit
/// (Assets, COGS, Expenses). This mirrors the identical helper in <see cref="LedgerService"/>
/// and is the single source of truth for how <see cref="DebitAsync"/> and <see cref="CreditAsync"/>
/// decide the direction of the balance adjustment.
/// </summary>
private static bool IsNormalDebitBalance(AccountSubType subType) => subType switch
{
AccountSubType.Cash
or AccountSubType.Checking
or AccountSubType.Savings
or AccountSubType.AccountsReceivable
or AccountSubType.Inventory
or AccountSubType.FixedAsset
or AccountSubType.OtherCurrentAsset
or AccountSubType.OtherAsset => true,
AccountSubType.CostOfGoodsSold => true,
// Expense subtypes (enum values ≥ 50) → normal debit balance
var st when (int)st >= 50 => true,
_ => false
};
}