68 lines
2.9 KiB
C#
68 lines
2.9 KiB
C#
using General_Ledger_Service.Exceptions;
|
|
using General_Ledger_Service.Interfaces.Repositories;
|
|
using General_Ledger_Service.Models;
|
|
|
|
namespace General_Ledger_Service.Services
|
|
{
|
|
// Extracted from AccountService (2026-07-31) so BankService's auto-created "Bank"/"Cash" GL
|
|
// hierarchy (see BankService.CreateBankAccountAsync/CreateCashAccountAsync) can reuse the same
|
|
// Rule 8.1 code-generation scheme instead of duplicating it - same shared-static-helper shape
|
|
// as RecursiveTreeQueries/DepthGuard (CLAUDE.md Rule 11), not a base class.
|
|
public static class AccountCodeGenerator
|
|
{
|
|
private static readonly Dictionary<short, int> AccountTypeBlockStart = new()
|
|
{
|
|
[1] = 1000, // Asset
|
|
[2] = 2000, // Liability
|
|
[3] = 3000, // Equity
|
|
[4] = 4000, // Income
|
|
[5] = 5000 // Expense
|
|
};
|
|
|
|
public static async Task<string> GenerateAsync(IAccountRepository accountRepository, short accountTypeId, Account? parent)
|
|
{
|
|
if (parent != null)
|
|
{
|
|
for (var suffix = 1; suffix <= 99; suffix++)
|
|
{
|
|
var code = $"{parent.AccountCode}-{suffix:D2}";
|
|
|
|
if (code.Length > 20)
|
|
{
|
|
throw new BadRequestException("Cannot generate an account code: nesting is too deep for the 20-character account code column.");
|
|
}
|
|
|
|
if (await accountRepository.GetByCodeAsync(code) is null)
|
|
{
|
|
return code;
|
|
}
|
|
}
|
|
|
|
throw new BadRequestException("Maximum number of child accounts under this parent has been reached.");
|
|
}
|
|
|
|
if (!AccountTypeBlockStart.TryGetValue(accountTypeId, out var blockStart))
|
|
{
|
|
throw new BadRequestException(
|
|
$"Invalid accountTypeId '{accountTypeId}'. Must be one of: 1 (Asset), 2 (Liability), 3 (Equity), 4 (Income), 5 (Expense).");
|
|
}
|
|
|
|
// Probes each of the 10 designed slots directly rather than trusting a root count,
|
|
// since pre-existing accounts created before this generation scheme existed (manually
|
|
// coded, e.g. "5100") don't necessarily occupy slots in count order - a count-based
|
|
// guess can collide with one of those and crash on the DB's unique constraint instead
|
|
// of failing cleanly.
|
|
for (var slot = 0; slot < 10; slot++)
|
|
{
|
|
var code = (blockStart + slot * 100).ToString();
|
|
if (await accountRepository.GetByCodeAsync(code) is null)
|
|
{
|
|
return code;
|
|
}
|
|
}
|
|
|
|
throw new BadRequestException("Maximum number of root-level accounts for this account type has been reached.");
|
|
}
|
|
}
|
|
}
|