This commit includes all previously untracked backend files: Domain: - Accounts, Attachments, BankConnections, Customers - FiscalYears, Invoices, JournalEntryDrafts - Orders, Products, UserAccess Commands & Handlers: - Full CQRS command structure for all domains Repositories: - PostgreSQL repositories for all read models - Bank transaction and ledger repositories GraphQL: - Input types, scalars, and types for all entities - Mutations and queries Infrastructure: - Banking integration (Enable Banking client) - File storage, Invoicing, Reporting, SAF-T export - Database migrations (003-029) Tests: - Integration tests for GraphQL endpoints - Domain tests - Invoicing and reporting tests Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
115 lines
3.8 KiB
C#
115 lines
3.8 KiB
C#
using System.Text;
|
|
|
|
namespace Books.Api.AiBookkeeper;
|
|
|
|
/// <summary>
|
|
/// Converts ChartOfAccountsDto to .toon format for AI Bookkeeper.
|
|
/// The .toon format is a structured text format with meta and accounts sections.
|
|
/// </summary>
|
|
public static class ToonFormatConverter
|
|
{
|
|
/// <summary>
|
|
/// Convert a chart of accounts to .toon format string.
|
|
/// </summary>
|
|
public static string ConvertToToon(ChartOfAccountsDto chartOfAccounts)
|
|
{
|
|
var sb = new StringBuilder();
|
|
|
|
// Header comment
|
|
sb.AppendLine("# Chart of Accounts for AI Bookkeeper");
|
|
sb.AppendLine();
|
|
|
|
// Meta section
|
|
sb.AppendLine("meta:");
|
|
sb.AppendLine(" source: Books API");
|
|
sb.AppendLine($" organizationId: {chartOfAccounts.CompanyId}");
|
|
sb.AppendLine(" accountType: expense");
|
|
sb.AppendLine($" totalAccounts: {chartOfAccounts.Accounts.Count}");
|
|
sb.AppendLine();
|
|
|
|
// Accounts section
|
|
// Format: number,name,category,vatCode,region,vatRubric,suggestions
|
|
sb.AppendLine($"accounts[{chartOfAccounts.Accounts.Count}]{{number,name,category,vatCode,region,vatRubric,suggestions}}:");
|
|
|
|
foreach (var account in chartOfAccounts.Accounts)
|
|
{
|
|
var vatCode = MapVatCode(account.VatCodeId);
|
|
var region = DetermineRegion(account.VatCodeId);
|
|
var category = MapCategory(account.AccountType);
|
|
var suggestions = GenerateSuggestions(account.Name, account.AccountNumber);
|
|
|
|
// Format: number,name,category,vatCode,region,vatRubric,suggestions
|
|
sb.AppendLine($" {account.AccountNumber},{EscapeCommas(account.Name)},{category},{vatCode},{region},,{suggestions}");
|
|
}
|
|
|
|
return sb.ToString();
|
|
}
|
|
|
|
public static string MapVatCode(string? vatCodeId)
|
|
{
|
|
if (string.IsNullOrEmpty(vatCodeId))
|
|
return "";
|
|
|
|
return vatCodeId.ToUpperInvariant() switch
|
|
{
|
|
"I25" => "I25",
|
|
"U25" => "", // Output VAT not relevant for expense accounts
|
|
"INGEN" => "",
|
|
_ => vatCodeId
|
|
};
|
|
}
|
|
|
|
public static string DetermineRegion(string? vatCodeId)
|
|
{
|
|
if (string.IsNullOrEmpty(vatCodeId))
|
|
return "";
|
|
|
|
return vatCodeId.ToUpperInvariant() switch
|
|
{
|
|
"IEUV" => "EU", // EU goods
|
|
"IEUY" => "EU", // EU services
|
|
"IVV" => "WORLD", // World goods
|
|
"IVY" => "WORLD", // World services
|
|
_ => "" // Empty = available for all regions
|
|
};
|
|
}
|
|
|
|
public static string MapCategory(string accountType)
|
|
{
|
|
return accountType switch
|
|
{
|
|
"expense" => "Administrationsomkostninger",
|
|
"cogs" => "Variable omkostninger",
|
|
"personnel" => "Lønomkostninger",
|
|
"financial" => "Renteudgifter",
|
|
_ => "Øvrige omkostninger"
|
|
};
|
|
}
|
|
|
|
public static string GenerateSuggestions(string name, string accountNumber)
|
|
{
|
|
// Generate search keywords from account name
|
|
var suggestions = new List<string>();
|
|
|
|
// Add words from name (lowercase, no special chars)
|
|
var words = name.ToLowerInvariant()
|
|
.Replace(",", " ")
|
|
.Replace(".", " ")
|
|
.Replace("-", " ")
|
|
.Split(' ', StringSplitOptions.RemoveEmptyEntries)
|
|
.Where(w => w.Length > 2);
|
|
|
|
suggestions.AddRange(words);
|
|
|
|
// Add account number as suggestion
|
|
suggestions.Add(accountNumber);
|
|
|
|
return string.Join("|", suggestions.Distinct());
|
|
}
|
|
|
|
private static string EscapeCommas(string value)
|
|
{
|
|
// The .toon format uses commas as delimiters, so we need to handle commas in values
|
|
return value.Replace(",", " ");
|
|
}
|
|
}
|