using System.Text; namespace Books.Api.AiBookkeeper; /// /// Converts ChartOfAccountsDto to .toon format for AI Bookkeeper. /// The .toon format is a structured text format with meta and accounts sections. /// public static class ToonFormatConverter { /// /// Convert a chart of accounts to .toon format string. /// 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(); // 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(",", " "); } }