Files
Distribution_GL/AppDBContext.cs
T

1170 lines
57 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Text;
using General_Ledger_Service.Enums;
using General_Ledger_Service.Models;
using Microsoft.EntityFrameworkCore;
namespace General_Ledger_Service.Infra
{
public class AppDBContext : DbContext
{
public AppDBContext(DbContextOptions<AppDBContext> options) : base(options)
{
}
public DbSet<AccountType> AccountTypes => Set<AccountType>();
public DbSet<Account> Accounts => Set<Account>();
public DbSet<FiscalYear> FiscalYears => Set<FiscalYear>();
public DbSet<AccountingPeriod> AccountingPeriods => Set<AccountingPeriod>();
public DbSet<PeriodLockLog> PeriodLockLogs => Set<PeriodLockLog>();
public DbSet<JournalEntry> JournalEntries => Set<JournalEntry>();
public DbSet<JournalEntryLine> JournalEntryLines => Set<JournalEntryLine>();
public DbSet<JournalCorrection> JournalCorrections => Set<JournalCorrection>();
public DbSet<CostCenter> CostCenters => Set<CostCenter>();
public DbSet<FixedAsset> FixedAssets => Set<FixedAsset>();
public DbSet<DepreciationSchedule> DepreciationSchedules => Set<DepreciationSchedule>();
public DbSet<AssetDisposal> AssetDisposals => Set<AssetDisposal>();
public DbSet<BankAccount> BankAccounts => Set<BankAccount>();
public DbSet<BankStatementLine> BankStatementLines => Set<BankStatementLine>();
public DbSet<CashAccount> CashAccounts => Set<CashAccount>();
public DbSet<CashAccountType> CashAccountTypes => Set<CashAccountType>();
public DbSet<TaxType> TaxTypes => Set<TaxType>();
public DbSet<TaxCode> TaxCodes => Set<TaxCode>();
public DbSet<Currency> Currencies => Set<Currency>();
public DbSet<ExchangeRate> ExchangeRates => Set<ExchangeRate>();
public DbSet<CurrencyRoundingRule> CurrencyRoundingRules => Set<CurrencyRoundingRule>();
public DbSet<SystemConfig> SystemConfigs => Set<SystemConfig>();
public DbSet<Budget> Budgets => Set<Budget>();
public DbSet<BudgetLine> BudgetLines => Set<BudgetLine>();
public DbSet<RecurringJournalTemplate> RecurringJournalTemplates => Set<RecurringJournalTemplate>();
public DbSet<RecurringJournalTemplateLine> RecurringJournalTemplateLines => Set<RecurringJournalTemplateLine>();
public DbSet<AuditLog> AuditLogs => Set<AuditLog>();
public DbSet<ChequeBook> ChequeBooks => Set<ChequeBook>();
public DbSet<ChequePage> ChequePages => Set<ChequePage>();
public DbSet<ReceivedCheque> ReceivedCheques => Set<ReceivedCheque>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
ConfigureAccountType(modelBuilder);
ConfigureAccount(modelBuilder);
ConfigureFiscalYear(modelBuilder);
ConfigureAccountingPeriod(modelBuilder);
ConfigurePeriodLockLog(modelBuilder);
ConfigureJournalEntry(modelBuilder);
ConfigureJournalEntryLine(modelBuilder);
ConfigureJournalCorrection(modelBuilder);
ConfigureCostCenter(modelBuilder);
ConfigureFixedAsset(modelBuilder);
ConfigureDepreciationSchedule(modelBuilder);
ConfigureAssetDisposal(modelBuilder);
ConfigureBankAccount(modelBuilder);
ConfigureBankStatementLine(modelBuilder);
ConfigureCashAccountType(modelBuilder);
ConfigureCashAccount(modelBuilder);
ConfigureTaxType(modelBuilder);
ConfigureTaxCode(modelBuilder);
ConfigureCurrency(modelBuilder);
ConfigureExchangeRate(modelBuilder);
ConfigureCurrencyRoundingRule(modelBuilder);
ConfigureSystemConfig(modelBuilder);
ConfigureBudget(modelBuilder);
ConfigureBudgetLine(modelBuilder);
ConfigureRecurringJournalTemplate(modelBuilder);
ConfigureRecurringJournalTemplateLine(modelBuilder);
ConfigureAuditLog(modelBuilder);
ConfigureChequeBook(modelBuilder);
ConfigureChequePage(modelBuilder);
ConfigureReceivedCheque(modelBuilder);
SeedReferenceData(modelBuilder);
ApplyDecimalPrecision(modelBuilder);
// The reference design (Accounting_System_Design_LKAS.md, the ER diagram, and every
// recursive CTE query example in both) uses snake_case table/column names throughout.
// Table names are set explicitly above; this pass converts the remaining EF Core
// default PascalCase identifiers (columns, keys, FKs, indexes) to match.
ApplySnakeCaseNaming(modelBuilder);
}
// MySQL has no unbounded numeric (CLAUDE.md Rule 13 on PostgreSQL relied on one), and
// Pomelo's default for an unconfigured decimal is decimal(18,2), which would silently
// round on storage. Every decimal column - amounts and rates alike - is DECIMAL(38,10):
// the same 10-decimal scale the report SQL already ROUNDs to.
private static void ApplyDecimalPrecision(ModelBuilder modelBuilder)
{
foreach (var entity in modelBuilder.Model.GetEntityTypes())
{
foreach (var property in entity.GetProperties())
{
if (property.ClrType == typeof(decimal) || property.ClrType == typeof(decimal?))
{
property.SetPrecision(38);
property.SetScale(10);
}
}
}
}
private static void ApplySnakeCaseNaming(ModelBuilder modelBuilder)
{
foreach (var entity in modelBuilder.Model.GetEntityTypes())
{
foreach (var property in entity.GetProperties())
{
property.SetColumnName(ToSnakeCase(property.GetColumnName()));
}
foreach (var key in entity.GetKeys())
{
key.SetName(ToSnakeCase(key.GetName()!));
}
foreach (var foreignKey in entity.GetForeignKeys())
{
foreignKey.SetConstraintName(ToSnakeCase(foreignKey.GetConstraintName()!));
}
foreach (var index in entity.GetIndexes())
{
index.SetDatabaseName(ToSnakeCase(index.GetDatabaseName()!));
}
}
}
private static string ToSnakeCase(string input)
{
var builder = new StringBuilder();
for (var i = 0; i < input.Length; i++)
{
var c = input[i];
if (char.IsUpper(c))
{
if (i > 0) builder.Append('_');
builder.Append(char.ToLowerInvariant(c));
}
else
{
builder.Append(c);
}
}
return builder.ToString();
}
private static void ConfigureAccountType(ModelBuilder modelBuilder)
{
modelBuilder.Entity<AccountType>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.Id).ValueGeneratedNever();
entity.Property(e => e.Name).HasMaxLength(50).IsRequired();
entity.Property(e => e.NormalBalance).HasMaxLength(2).HasColumnType("char(2)").IsRequired();
entity.Property(e => e.CreatedAt).HasColumnType("datetime(6)").HasDefaultValueSql("CURRENT_TIMESTAMP(6)");
entity.ToTable("account_type", t => t.HasCheckConstraint("CK_account_type_normal_balance", "normal_balance IN ('DR','CR')"));
});
}
private static void ConfigureAccount(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Account>(entity =>
{
entity.ToTable("accounts");
entity.HasKey(e => e.AccountId);
entity.Property(e => e.AccountCode).HasMaxLength(20).IsRequired();
entity.Property(e => e.AccountName).HasMaxLength(150).IsRequired();
entity.Property(e => e.CurrencyCode).HasMaxLength(3).HasColumnType("char(3)").HasDefaultValue("LKR");
entity.Property(e => e.CashFlowCategory).HasMaxLength(15);
entity.Property(e => e.IncomeStatementSection).HasMaxLength(25);
entity.Property(e => e.BalanceSheetClassification).HasMaxLength(25);
entity.Property(e => e.IsTaxDeductible).HasDefaultValue(true);
entity.Property(e => e.IsControlAccount).HasDefaultValue(false);
entity.Property(e => e.IsPostable).HasDefaultValue(true);
entity.Property(e => e.IsActive).HasDefaultValue(true);
entity.Property(e => e.CreatedAt).HasColumnType("datetime(6)").HasDefaultValueSql("CURRENT_TIMESTAMP(6)");
entity.HasIndex(e => e.AccountCode).IsUnique();
entity.HasIndex(e => e.ParentAccountId);
entity.HasOne(e => e.ParentAccount)
.WithMany()
.HasForeignKey(e => e.ParentAccountId)
.OnDelete(DeleteBehavior.Restrict);
entity.HasOne(e => e.AccountType)
.WithMany()
.HasForeignKey(e => e.AccountTypeId)
.OnDelete(DeleteBehavior.Restrict);
entity.HasOne(e => e.Currency)
.WithMany()
.HasForeignKey(e => e.CurrencyCode)
.OnDelete(DeleteBehavior.Restrict);
});
}
private static void ConfigureFiscalYear(ModelBuilder modelBuilder)
{
modelBuilder.Entity<FiscalYear>(entity =>
{
entity.ToTable("fiscal_year");
entity.HasKey(e => e.FiscalYearId);
entity.Property(e => e.YearName).HasMaxLength(20).IsRequired();
entity.Property(e => e.IsClosed).HasDefaultValue(false);
entity.Property(e => e.CreatedAt).HasColumnType("datetime(6)").HasDefaultValueSql("CURRENT_TIMESTAMP(6)");
});
}
private static void ConfigureAccountingPeriod(ModelBuilder modelBuilder)
{
modelBuilder.Entity<AccountingPeriod>(entity =>
{
entity.ToTable("accounting_period");
entity.HasKey(e => e.PeriodId);
entity.Property(e => e.Status).HasDefaultValue(PeriodStatus.Open);
entity.Property(e => e.LockReason).HasMaxLength(255);
entity.Property(e => e.LockGl).HasDefaultValue(false);
entity.Property(e => e.CreatedAt).HasColumnType("datetime(6)").HasDefaultValueSql("CURRENT_TIMESTAMP(6)");
entity.HasOne(e => e.FiscalYear)
.WithMany()
.HasForeignKey(e => e.FiscalYearId)
.OnDelete(DeleteBehavior.Restrict);
});
}
private static void ConfigurePeriodLockLog(ModelBuilder modelBuilder)
{
modelBuilder.Entity<PeriodLockLog>(entity =>
{
entity.ToTable("period_lock_log");
entity.HasKey(e => e.LogId);
entity.Property(e => e.ModuleLocked).HasMaxLength(15);
entity.Property(e => e.Reason).HasMaxLength(255);
entity.Property(e => e.CreatedAt).HasColumnType("datetime(6)").HasDefaultValueSql("CURRENT_TIMESTAMP(6)");
entity.HasOne(e => e.Period)
.WithMany()
.HasForeignKey(e => e.PeriodId)
.OnDelete(DeleteBehavior.Restrict);
});
}
private static void ConfigureJournalEntry(ModelBuilder modelBuilder)
{
modelBuilder.Entity<JournalEntry>(entity =>
{
entity.ToTable("journal_entry");
entity.HasKey(e => e.JournalId);
entity.Property(e => e.JournalNo).HasMaxLength(30).IsRequired();
entity.Property(e => e.JournalType).HasMaxLength(30).IsRequired();
entity.Property(e => e.SourceModule).HasMaxLength(30);
entity.Property(e => e.Reference).HasMaxLength(100);
entity.Property(e => e.Narration).HasColumnType("text");
entity.Property(e => e.IsPosted).HasDefaultValue(false);
entity.Property(e => e.IsReversed).HasDefaultValue(false);
// No DB-level default: every entry is always created explicitly as Draft
// by the Service layer (draft -> post workflow), unlike the design doc's
// ALTER TABLE default of 'POSTED', which assumed direct-posted entries.
entity.Property(e => e.EntryStatus).IsRequired();
entity.Property(e => e.PreparedBy).HasMaxLength(100);
entity.Property(e => e.CreatedAt).HasColumnType("datetime(6)").HasDefaultValueSql("CURRENT_TIMESTAMP(6)");
entity.HasIndex(e => e.JournalNo).IsUnique();
entity.HasOne(e => e.Period)
.WithMany()
.HasForeignKey(e => e.PeriodId)
.OnDelete(DeleteBehavior.Restrict);
entity.HasOne(e => e.ReversalOf)
.WithMany()
.HasForeignKey(e => e.ReversalOfId)
.OnDelete(DeleteBehavior.Restrict);
});
}
private static void ConfigureJournalEntryLine(ModelBuilder modelBuilder)
{
modelBuilder.Entity<JournalEntryLine>(entity =>
{
entity.HasKey(e => e.LineId);
entity.Property(e => e.DebitAmount).HasDefaultValue(0m);
entity.Property(e => e.CreditAmount).HasDefaultValue(0m);
entity.Property(e => e.CurrencyCode).HasMaxLength(3).HasColumnType("char(3)").HasDefaultValue("LKR");
entity.Property(e => e.ExchangeRate).HasDefaultValue(1m);
entity.Property(e => e.BaseDebit).HasDefaultValue(0m);
entity.Property(e => e.BaseCredit).HasDefaultValue(0m);
entity.Property(e => e.Memo).HasColumnType("text");
entity.Property(e => e.CreatedAt).HasColumnType("datetime(6)").HasDefaultValueSql("CURRENT_TIMESTAMP(6)");
entity.ToTable("journal_entry_line", t =>
{
t.HasCheckConstraint("CK_journal_entry_line_amounts_non_negative",
"debit_amount >= 0 AND credit_amount >= 0");
t.HasCheckConstraint("CK_journal_entry_line_one_sided",
"(debit_amount = 0 AND credit_amount > 0) OR (credit_amount = 0 AND debit_amount > 0)");
});
entity.HasOne(e => e.Journal)
.WithMany(e => e.Lines)
.HasForeignKey(e => e.JournalId)
.OnDelete(DeleteBehavior.Cascade);
entity.HasOne(e => e.Account)
.WithMany()
.HasForeignKey(e => e.AccountId)
.OnDelete(DeleteBehavior.Restrict);
entity.HasOne(e => e.CostCenter)
.WithMany()
.HasForeignKey(e => e.CostCenterId)
.OnDelete(DeleteBehavior.Restrict);
entity.HasOne(e => e.Currency)
.WithMany()
.HasForeignKey(e => e.CurrencyCode)
.OnDelete(DeleteBehavior.Restrict);
});
}
private static void ConfigureJournalCorrection(ModelBuilder modelBuilder)
{
modelBuilder.Entity<JournalCorrection>(entity =>
{
entity.ToTable("journal_correction");
entity.HasKey(e => e.CorrectionId);
entity.Property(e => e.CorrectionType).IsRequired();
entity.Property(e => e.Reason).HasColumnType("text").IsRequired();
entity.Property(e => e.CorrectedBy).HasMaxLength(100);
entity.Property(e => e.CreatedAt).HasColumnType("datetime(6)").HasDefaultValueSql("CURRENT_TIMESTAMP(6)");
entity.HasOne(e => e.OriginalJournal)
.WithMany()
.HasForeignKey(e => e.OriginalJournalId)
.OnDelete(DeleteBehavior.Restrict);
entity.HasOne(e => e.CorrectionJournal)
.WithMany()
.HasForeignKey(e => e.CorrectionJournalId)
.OnDelete(DeleteBehavior.Restrict);
});
}
private static void ConfigureCostCenter(ModelBuilder modelBuilder)
{
modelBuilder.Entity<CostCenter>(entity =>
{
entity.ToTable("cost_center");
entity.HasKey(e => e.CostCenterId);
entity.Property(e => e.Code).HasMaxLength(20).IsRequired();
entity.Property(e => e.Name).HasMaxLength(150).IsRequired();
entity.Property(e => e.IsActive).HasDefaultValue(true);
entity.Property(e => e.CreatedAt).HasColumnType("datetime(6)").HasDefaultValueSql("CURRENT_TIMESTAMP(6)");
entity.HasIndex(e => e.Code).IsUnique();
entity.HasIndex(e => e.ParentCostCenterId);
entity.HasOne(e => e.ParentCostCenter)
.WithMany()
.HasForeignKey(e => e.ParentCostCenterId)
.OnDelete(DeleteBehavior.Restrict);
});
}
private static void ConfigureFixedAsset(ModelBuilder modelBuilder)
{
modelBuilder.Entity<FixedAsset>(entity =>
{
entity.ToTable("fixed_asset");
entity.HasKey(e => e.AssetId);
entity.Property(e => e.AssetCode).HasMaxLength(30).IsRequired();
entity.Property(e => e.AssetName).HasMaxLength(150).IsRequired();
entity.Property(e => e.ResidualValue).HasDefaultValue(0m);
entity.Property(e => e.DepreciationMethod).HasDefaultValue(Enums.DepreciationMethod.StraightLine);
entity.Property(e => e.Status).HasDefaultValue(FixedAssetStatus.Active);
entity.Property(e => e.CreatedAt).HasColumnType("datetime(6)").HasDefaultValueSql("CURRENT_TIMESTAMP(6)");
entity.HasIndex(e => e.AssetCode).IsUnique();
entity.HasOne(e => e.AssetAccount)
.WithMany()
.HasForeignKey(e => e.AssetAccountId)
.OnDelete(DeleteBehavior.Restrict);
entity.HasOne(e => e.AccumDeprAccount)
.WithMany()
.HasForeignKey(e => e.AccumDeprAccountId)
.OnDelete(DeleteBehavior.Restrict);
entity.HasOne(e => e.DeprExpenseAccount)
.WithMany()
.HasForeignKey(e => e.DeprExpenseAccountId)
.OnDelete(DeleteBehavior.Restrict);
entity.HasOne(e => e.GainLossAccount)
.WithMany()
.HasForeignKey(e => e.GainLossAccountId)
.OnDelete(DeleteBehavior.Restrict);
});
}
private static void ConfigureDepreciationSchedule(ModelBuilder modelBuilder)
{
modelBuilder.Entity<DepreciationSchedule>(entity =>
{
entity.ToTable("depreciation_schedule");
entity.HasKey(e => e.ScheduleId);
entity.Property(e => e.CreatedAt).HasColumnType("datetime(6)").HasDefaultValueSql("CURRENT_TIMESTAMP(6)");
entity.HasOne(e => e.Asset)
.WithMany()
.HasForeignKey(e => e.AssetId)
.OnDelete(DeleteBehavior.Restrict);
entity.HasOne(e => e.Period)
.WithMany()
.HasForeignKey(e => e.PeriodId)
.OnDelete(DeleteBehavior.Restrict);
entity.HasOne(e => e.Journal)
.WithMany()
.HasForeignKey(e => e.JournalId)
.OnDelete(DeleteBehavior.Restrict);
});
}
private static void ConfigureAssetDisposal(ModelBuilder modelBuilder)
{
modelBuilder.Entity<AssetDisposal>(entity =>
{
entity.ToTable("asset_disposal");
entity.HasKey(e => e.DisposalId);
entity.Property(e => e.CreatedAt).HasColumnType("datetime(6)").HasDefaultValueSql("CURRENT_TIMESTAMP(6)");
entity.HasOne(e => e.Asset)
.WithMany()
.HasForeignKey(e => e.AssetId)
.OnDelete(DeleteBehavior.Restrict);
entity.HasOne(e => e.Journal)
.WithMany()
.HasForeignKey(e => e.JournalId)
.OnDelete(DeleteBehavior.Restrict);
});
}
private static void ConfigureBankAccount(ModelBuilder modelBuilder)
{
modelBuilder.Entity<BankAccount>(entity =>
{
entity.ToTable("bank_account");
entity.HasKey(e => e.BankAccountId);
entity.Property(e => e.AccountName).HasMaxLength(150).IsRequired();
entity.Property(e => e.BankName).HasMaxLength(150);
entity.Property(e => e.AccountNumber).HasMaxLength(50);
entity.Property(e => e.CurrencyCode).HasMaxLength(3).HasColumnType("char(3)").HasDefaultValue("LKR");
entity.Property(e => e.CreatedAt).HasColumnType("datetime(6)").HasDefaultValueSql("CURRENT_TIMESTAMP(6)");
entity.HasOne(e => e.GlAccount)
.WithMany()
.HasForeignKey(e => e.GlAccountId)
.OnDelete(DeleteBehavior.Restrict);
entity.HasOne(e => e.Currency)
.WithMany()
.HasForeignKey(e => e.CurrencyCode)
.OnDelete(DeleteBehavior.Restrict);
});
}
private static void ConfigureCashAccountType(ModelBuilder modelBuilder)
{
modelBuilder.Entity<CashAccountType>(entity =>
{
entity.ToTable("cash_account_type");
entity.HasKey(e => e.CashAccountTypeId);
entity.Property(e => e.Name).HasMaxLength(100).IsRequired();
entity.Property(e => e.CreatedAt).HasColumnType("datetime(6)").HasDefaultValueSql("CURRENT_TIMESTAMP(6)");
entity.HasIndex(e => e.Name).IsUnique();
entity.HasOne(e => e.GlAccount)
.WithMany()
.HasForeignKey(e => e.GlAccountId)
.OnDelete(DeleteBehavior.Restrict);
});
}
private static void ConfigureCashAccount(ModelBuilder modelBuilder)
{
modelBuilder.Entity<CashAccount>(entity =>
{
entity.ToTable("cash_account");
entity.HasKey(e => e.CashAccountId);
entity.Property(e => e.AccountName).HasMaxLength(150).IsRequired();
entity.Property(e => e.AccountNumber).HasMaxLength(50).IsRequired();
entity.Property(e => e.CurrencyCode).HasMaxLength(3).HasColumnType("char(3)").HasDefaultValue("LKR");
entity.Property(e => e.IsActive).HasDefaultValue(true);
entity.Property(e => e.IsDefault).HasDefaultValue(false);
entity.Property(e => e.CreatedAt).HasColumnType("datetime(6)").HasDefaultValueSql("CURRENT_TIMESTAMP(6)");
entity.HasIndex(e => e.AccountNumber).IsUnique();
entity.HasOne(e => e.CashAccountType)
.WithMany()
.HasForeignKey(e => e.CashAccountTypeId)
.OnDelete(DeleteBehavior.Restrict);
entity.HasOne(e => e.GlAccount)
.WithMany()
.HasForeignKey(e => e.GlAccountId)
.OnDelete(DeleteBehavior.Restrict);
entity.HasOne(e => e.Currency)
.WithMany()
.HasForeignKey(e => e.CurrencyCode)
.OnDelete(DeleteBehavior.Restrict);
});
}
private static void ConfigureBankStatementLine(ModelBuilder modelBuilder)
{
modelBuilder.Entity<BankStatementLine>(entity =>
{
entity.ToTable("bank_statement_line");
entity.HasKey(e => e.StmtLineId);
entity.Property(e => e.Description).HasMaxLength(255);
entity.Property(e => e.IsReconciled).HasDefaultValue(false);
entity.Property(e => e.CreatedAt).HasColumnType("datetime(6)").HasDefaultValueSql("CURRENT_TIMESTAMP(6)");
entity.HasOne(e => e.BankAccount)
.WithMany()
.HasForeignKey(e => e.BankAccountId)
.OnDelete(DeleteBehavior.Restrict);
entity.HasOne(e => e.MatchedJournal)
.WithMany()
.HasForeignKey(e => e.MatchedJournalId)
.OnDelete(DeleteBehavior.Restrict);
});
}
private static void ConfigureTaxType(ModelBuilder modelBuilder)
{
modelBuilder.Entity<TaxType>(entity =>
{
entity.ToTable("tax_type");
entity.HasKey(e => e.TaxTypeId);
entity.Property(e => e.Code).HasMaxLength(20).IsRequired();
entity.Property(e => e.Name).HasMaxLength(100).IsRequired();
entity.Property(e => e.CreatedAt).HasColumnType("datetime(6)").HasDefaultValueSql("CURRENT_TIMESTAMP(6)");
entity.HasIndex(e => e.Code).IsUnique();
});
}
private static void ConfigureTaxCode(ModelBuilder modelBuilder)
{
modelBuilder.Entity<TaxCode>(entity =>
{
entity.ToTable("tax_code");
entity.HasKey(e => e.TaxCodeId);
entity.Property(e => e.Code).HasMaxLength(20).IsRequired();
entity.Property(e => e.Name).HasMaxLength(100).IsRequired();
entity.Property(e => e.CalculationBasis).HasDefaultValue(TaxCalculationBasis.Exclusive);
entity.Property(e => e.AppliesTo).IsRequired();
entity.Property(e => e.IsActive).HasDefaultValue(true);
entity.Property(e => e.CreatedAt).HasColumnType("datetime(6)").HasDefaultValueSql("CURRENT_TIMESTAMP(6)");
entity.HasIndex(e => e.Code).IsUnique();
entity.HasOne(e => e.TaxType)
.WithMany()
.HasForeignKey(e => e.TaxTypeId)
.OnDelete(DeleteBehavior.Restrict);
entity.HasOne(e => e.OutputTaxAccount)
.WithMany()
.HasForeignKey(e => e.OutputTaxAccountId)
.OnDelete(DeleteBehavior.Restrict);
entity.HasOne(e => e.InputTaxAccount)
.WithMany()
.HasForeignKey(e => e.InputTaxAccountId)
.OnDelete(DeleteBehavior.Restrict);
});
}
private static void ConfigureCurrency(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Currency>(entity =>
{
entity.ToTable("currency");
entity.HasKey(e => e.CurrencyCode);
entity.Property(e => e.CurrencyCode).HasMaxLength(3).HasColumnType("char(3)").ValueGeneratedNever();
entity.Property(e => e.CurrencyName).HasMaxLength(50).IsRequired();
entity.Property(e => e.Symbol).HasMaxLength(5);
// No HasDefaultValue on DecimalPlaces: every Currency row sets it explicitly
// (HasData seed, and any future create endpoint), and a configured default here
// makes EF Core treat an explicit 0 (JPY, KRW, ...) as "unset" during HasData
// diffing, silently dropping the column from the generated INSERT.
entity.Property(e => e.IsBaseCurrency).HasDefaultValue(false);
entity.Property(e => e.IsActive).HasDefaultValue(true);
entity.Property(e => e.CreatedAt).HasColumnType("datetime(6)").HasDefaultValueSql("CURRENT_TIMESTAMP(6)");
// "Exactly one base currency" is enforced by a functional unique index on
// (CASE WHEN is_base_currency THEN 1 END), created via raw SQL in the
// InitialMySql migration - MySQL has no partial (filtered) indexes, so EF can't
// model it. The CASE yields NULL for non-base rows, and NULLs may repeat.
});
}
private static void ConfigureExchangeRate(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ExchangeRate>(entity =>
{
entity.ToTable("exchange_rate");
entity.HasKey(e => e.RateId);
entity.Property(e => e.CurrencyCode).HasMaxLength(3).HasColumnType("char(3)").IsRequired();
entity.Property(e => e.RateSource).HasMaxLength(30).HasDefaultValue("MANUAL");
entity.Property(e => e.CreatedAt).HasColumnType("datetime(6)").HasDefaultValueSql("CURRENT_TIMESTAMP(6)");
entity.HasIndex(e => new { e.CurrencyCode, e.RateDate }).IsUnique();
entity.HasOne(e => e.Currency)
.WithMany()
.HasForeignKey(e => e.CurrencyCode)
.OnDelete(DeleteBehavior.Restrict);
});
}
private static void ConfigureCurrencyRoundingRule(ModelBuilder modelBuilder)
{
modelBuilder.Entity<CurrencyRoundingRule>(entity =>
{
entity.ToTable("currency_rounding_rule");
entity.HasKey(e => e.CurrencyCode);
entity.Property(e => e.CurrencyCode).HasMaxLength(3).HasColumnType("char(3)").ValueGeneratedNever();
entity.Property(e => e.RoundingMethod).HasMaxLength(15).HasDefaultValue("NEAREST");
entity.Property(e => e.CreatedAt).HasColumnType("datetime(6)").HasDefaultValueSql("CURRENT_TIMESTAMP(6)");
entity.HasOne(e => e.Currency)
.WithOne()
.HasForeignKey<CurrencyRoundingRule>(e => e.CurrencyCode)
.OnDelete(DeleteBehavior.Restrict);
entity.HasOne(e => e.RoundingAccount)
.WithMany()
.HasForeignKey(e => e.RoundingAccountId)
.OnDelete(DeleteBehavior.Restrict);
});
}
private static void ConfigureSystemConfig(ModelBuilder modelBuilder)
{
modelBuilder.Entity<SystemConfig>(entity =>
{
entity.ToTable("system_config");
entity.HasKey(e => e.ConfigKey);
entity.Property(e => e.ConfigKey).HasMaxLength(50).ValueGeneratedNever();
entity.Property(e => e.ConfigValue).HasMaxLength(255).IsRequired();
entity.Property(e => e.Description).HasColumnType("text");
entity.Property(e => e.IsEditable).HasDefaultValue(true);
entity.Property(e => e.IsLocked).HasDefaultValue(false);
entity.Property(e => e.CreatedAt).HasColumnType("datetime(6)").HasDefaultValueSql("CURRENT_TIMESTAMP(6)");
});
}
private static void ConfigureBudget(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Budget>(entity =>
{
entity.ToTable("budget");
entity.HasKey(e => e.BudgetId);
entity.Property(e => e.Name).HasMaxLength(100).IsRequired();
entity.Property(e => e.CreatedAt).HasColumnType("datetime(6)").HasDefaultValueSql("CURRENT_TIMESTAMP(6)");
entity.HasOne(e => e.FiscalYear)
.WithMany()
.HasForeignKey(e => e.FiscalYearId)
.OnDelete(DeleteBehavior.Restrict);
});
}
private static void ConfigureBudgetLine(ModelBuilder modelBuilder)
{
modelBuilder.Entity<BudgetLine>(entity =>
{
entity.ToTable("budget_line");
entity.HasKey(e => e.BudgetLineId);
entity.Property(e => e.CreatedAt).HasColumnType("datetime(6)").HasDefaultValueSql("CURRENT_TIMESTAMP(6)");
entity.HasOne(e => e.Budget)
.WithMany(e => e.Lines)
.HasForeignKey(e => e.BudgetId)
.OnDelete(DeleteBehavior.Cascade);
entity.HasOne(e => e.Account)
.WithMany()
.HasForeignKey(e => e.AccountId)
.OnDelete(DeleteBehavior.Restrict);
entity.HasOne(e => e.Period)
.WithMany()
.HasForeignKey(e => e.PeriodId)
.OnDelete(DeleteBehavior.Restrict);
entity.HasOne(e => e.CostCenter)
.WithMany()
.HasForeignKey(e => e.CostCenterId)
.OnDelete(DeleteBehavior.Restrict);
});
}
private static void ConfigureRecurringJournalTemplate(ModelBuilder modelBuilder)
{
modelBuilder.Entity<RecurringJournalTemplate>(entity =>
{
entity.ToTable("recurring_journal_template");
entity.HasKey(e => e.TemplateId);
entity.Property(e => e.TemplateName).HasMaxLength(150).IsRequired();
entity.Property(e => e.RecurrencePattern).IsRequired();
entity.Property(e => e.IsActive).HasDefaultValue(true);
entity.Property(e => e.CreatedAt).HasColumnType("datetime(6)").HasDefaultValueSql("CURRENT_TIMESTAMP(6)");
});
}
private static void ConfigureRecurringJournalTemplateLine(ModelBuilder modelBuilder)
{
modelBuilder.Entity<RecurringJournalTemplateLine>(entity =>
{
entity.ToTable("recurring_journal_template_line");
entity.HasKey(e => e.LineId);
entity.Property(e => e.DebitAmount).HasDefaultValue(0m);
entity.Property(e => e.CreditAmount).HasDefaultValue(0m);
entity.Property(e => e.CreatedAt).HasColumnType("datetime(6)").HasDefaultValueSql("CURRENT_TIMESTAMP(6)");
entity.HasOne(e => e.Template)
.WithMany(e => e.Lines)
.HasForeignKey(e => e.TemplateId)
.OnDelete(DeleteBehavior.Cascade);
entity.HasOne(e => e.Account)
.WithMany()
.HasForeignKey(e => e.AccountId)
.OnDelete(DeleteBehavior.Restrict);
});
}
private static void ConfigureAuditLog(ModelBuilder modelBuilder)
{
modelBuilder.Entity<AuditLog>(entity =>
{
entity.ToTable("audit_log");
entity.HasKey(e => e.AuditLogId);
entity.Property(e => e.EntityName).HasMaxLength(50).IsRequired();
entity.Property(e => e.EntityId).HasMaxLength(50).IsRequired();
entity.Property(e => e.Description).HasColumnType("text");
entity.Property(e => e.PerformedBy).HasMaxLength(100);
entity.Property(e => e.OccurredAt).HasColumnType("datetime(6)").HasDefaultValueSql("CURRENT_TIMESTAMP(6)");
// No FK to any other table by design — entity_name + entity_id is a loose
// reference spanning every table, not a single relationship.
});
}
private static void ConfigureChequeBook(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ChequeBook>(entity =>
{
entity.ToTable("cheque_book");
entity.HasKey(e => e.ChequeBookId);
entity.Property(e => e.ChequeBookNo).HasMaxLength(50).IsRequired();
entity.Property(e => e.StartChequeNo).HasMaxLength(30).IsRequired();
entity.Property(e => e.EndChequeNo).HasMaxLength(30).IsRequired();
entity.Property(e => e.Description).HasColumnType("text");
entity.Property(e => e.Status).HasDefaultValue(ChequeBookStatus.Active);
entity.Property(e => e.IsActive).HasDefaultValue(true);
entity.Property(e => e.CreatedBy).HasMaxLength(100);
entity.Property(e => e.ModifiedBy).HasMaxLength(100);
entity.Property(e => e.CreatedAt).HasColumnType("datetime(6)").HasDefaultValueSql("CURRENT_TIMESTAMP(6)");
entity.Property(e => e.ModifiedAt).HasColumnType("datetime(6)");
entity.HasIndex(e => e.ChequeBookNo).IsUnique();
entity.HasOne(e => e.BankAccount)
.WithMany()
.HasForeignKey(e => e.BankAccountId)
.OnDelete(DeleteBehavior.Restrict);
});
}
private static void ConfigureChequePage(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ChequePage>(entity =>
{
entity.ToTable("cheque_page");
entity.HasKey(e => e.ChequePageId);
entity.Property(e => e.ChequeNo).HasMaxLength(30).IsRequired();
entity.Property(e => e.IssueStatus).HasDefaultValue(ChequeIssueStatus.Unused);
entity.Property(e => e.PayeeName).HasMaxLength(200);
entity.Property(e => e.CurrencyCode).HasMaxLength(3).HasColumnType("char(3)");
entity.Property(e => e.ReferenceNo).HasMaxLength(100);
entity.Property(e => e.Purpose).HasMaxLength(500);
entity.Property(e => e.IsCrossCheque).HasDefaultValue(false);
entity.Property(e => e.IsAccountPayee).HasDefaultValue(false);
entity.Property(e => e.IsPostDated).HasDefaultValue(false);
entity.Property(e => e.ClearedByBank).HasDefaultValue(false);
entity.Property(e => e.CancelReason).HasMaxLength(500);
entity.Property(e => e.Notes).HasColumnType("text");
entity.Property(e => e.PrintedBy).HasMaxLength(100);
entity.Property(e => e.PrintedAt).HasColumnType("datetime(6)");
entity.Property(e => e.CreatedBy).HasMaxLength(100);
entity.Property(e => e.CreatedAt).HasColumnType("datetime(6)").HasDefaultValueSql("CURRENT_TIMESTAMP(6)");
entity.HasIndex(e => e.ChequeNo).IsUnique();
entity.HasOne(e => e.ChequeBook)
.WithMany(e => e.Pages)
.HasForeignKey(e => e.ChequeBookId)
.OnDelete(DeleteBehavior.Cascade);
entity.HasOne(e => e.Currency)
.WithMany()
.HasForeignKey(e => e.CurrencyCode)
.OnDelete(DeleteBehavior.Restrict);
});
}
private static void ConfigureReceivedCheque(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ReceivedCheque>(entity =>
{
entity.ToTable("received_cheque");
entity.HasKey(e => e.ReceivedChequeId);
entity.Property(e => e.ReceivedFromName).HasMaxLength(200).IsRequired();
entity.Property(e => e.DrawerBankName).HasMaxLength(150);
entity.Property(e => e.DrawerBankBranch).HasMaxLength(150);
entity.Property(e => e.AccountHolderName).HasMaxLength(200);
entity.Property(e => e.ChequeNo).HasMaxLength(30).IsRequired();
entity.Property(e => e.Status).HasDefaultValue(ReceivedChequeStatus.Received);
entity.Property(e => e.ReferenceType).HasMaxLength(100);
entity.Property(e => e.Notes).HasColumnType("text");
entity.Property(e => e.CreatedBy).HasMaxLength(100);
entity.Property(e => e.CreatedAt).HasColumnType("datetime(6)").HasDefaultValueSql("CURRENT_TIMESTAMP(6)");
entity.HasOne(e => e.DepositBankAccount)
.WithMany()
.HasForeignKey(e => e.DepositBankAccountId)
.OnDelete(DeleteBehavior.Restrict);
});
}
private static void SeedReferenceData(ModelBuilder modelBuilder)
{
// HasData requires static values resolvable at migration-design time, so seeded
// rows use a fixed timestamp here rather than the now() SQL default used elsewhere.
var seedTimestamp = new DateTime(2026, 1, 1, 0, 0, 0, DateTimeKind.Utc);
modelBuilder.Entity<AccountType>().HasData(
new AccountType { Id = 1, Name = "Asset", NormalBalance = "DR", CreatedAt = seedTimestamp },
new AccountType { Id = 2, Name = "Liability", NormalBalance = "CR", CreatedAt = seedTimestamp },
new AccountType { Id = 3, Name = "Equity", NormalBalance = "CR", CreatedAt = seedTimestamp },
new AccountType { Id = 4, Name = "Income", NormalBalance = "CR", CreatedAt = seedTimestamp },
new AccountType { Id = 5, Name = "Expense", NormalBalance = "DR", CreatedAt = seedTimestamp }
);
// Starter set (2026-07-22) - grows at runtime via BankService.CreateCashAccountAsync
// when a caller supplies a cashAccountTypeName not already in this table.
modelBuilder.Entity<CashAccountType>().HasData(
new CashAccountType { CashAccountTypeId = 1, Name = "Petty Cash", CreatedAt = seedTimestamp },
new CashAccountType { CashAccountTypeId = 2, Name = "Till Cash", CreatedAt = seedTimestamp },
new CashAccountType { CashAccountTypeId = 3, Name = "Safe Cash", CreatedAt = seedTimestamp },
new CashAccountType { CashAccountTypeId = 4, Name = "Cash in Transit", CreatedAt = seedTimestamp }
);
modelBuilder.Entity<SystemConfig>().HasData(
new SystemConfig
{
ConfigKey = "COA_MAX_DEPTH",
ConfigValue = "6",
Description = "Maximum allowed levels in the recursive Chart of Accounts tree",
IsEditable = true,
IsLocked = false,
CreatedAt = seedTimestamp
},
new SystemConfig
{
ConfigKey = "COST_CENTER_MAX_DEPTH",
ConfigValue = "4",
Description = "Maximum allowed levels in the recursive Cost Center tree",
IsEditable = true,
IsLocked = false,
CreatedAt = seedTimestamp
},
new SystemConfig
{
ConfigKey = "COA_ENFORCE_MAX_DEPTH",
ConfigValue = "TRUE",
Description = "If FALSE, depth is unlimited (config value ignored)",
IsEditable = true,
IsLocked = false,
CreatedAt = seedTimestamp
},
new SystemConfig
{
ConfigKey = "CASH_ACCOUNT_ROOT_ID",
// Deliberately left unset (empty, not a real account_id) - unlike the other
// three keys, this has to reference a real accounts.account_id, which doesn't
// exist until the company's own Chart of Accounts is created (Phase 2, an API
// call, not a seed). Set once via PUT /api/v1/system-config/CASH_ACCOUNT_ROOT_ID
// after COA setup, then locked - same one-time-edit rule as every other key here.
ConfigValue = "",
Description = "Root account (accounts.account_id) whose recursive descendants are treated as cash/bank for the Cash Flow Statement's opening/closing balance and for excluding cash accounts from working-capital changes",
IsEditable = true,
IsLocked = false,
CreatedAt = seedTimestamp
},
new SystemConfig
{
ConfigKey = "CORPORATE_INCOME_TAX_RATE",
ConfigValue = "30",
Description = "Default corporate income tax rate, percent, for the Income Tax Computation report - overridable per report request via taxRateOverride",
IsEditable = true,
IsLocked = false,
CreatedAt = seedTimestamp
},
new SystemConfig
{
ConfigKey = "STATUTORY_RELIEF_AMOUNT",
ConfigValue = "0",
Description = "Default Qualifying Payments/Reliefs figure for the Income Tax Computation report - overridable per report request via qualifyingPaymentsReliefs",
IsEditable = true,
IsLocked = false,
CreatedAt = seedTimestamp
},
new SystemConfig
{
ConfigKey = "APIT_CREDIT_ACCOUNT_ID",
// Same seeding constraint as CASH_ACCOUNT_ROOT_ID - references a real
// accounts.account_id that doesn't exist until the company's COA is built.
ConfigValue = "",
Description = "GL account (accounts.account_id) where APIT (Advance Personal Income Tax) credits are recorded, read by the Income Tax Computation report",
IsEditable = true,
IsLocked = false,
CreatedAt = seedTimestamp
},
new SystemConfig
{
ConfigKey = "WHT_CREDIT_ACCOUNT_ID",
ConfigValue = "",
Description = "GL account (accounts.account_id) where WHT credit receivable is recorded, read by the Income Tax Computation report",
IsEditable = true,
IsLocked = false,
CreatedAt = seedTimestamp
},
new SystemConfig
{
ConfigKey = "QUARTERLY_TAX_PAYMENT_ACCOUNT_ID",
ConfigValue = "",
Description = "GL account (accounts.account_id) where quarterly self-assessment tax installment payments are recorded, read by the Income Tax Computation report",
IsEditable = true,
IsLocked = false,
CreatedAt = seedTimestamp
}
);
modelBuilder.Entity<TaxType>().HasData(
new TaxType { TaxTypeId = 1, Code = "VAT", Name = "Value Added Tax", CreatedAt = seedTimestamp },
new TaxType { TaxTypeId = 2, Code = "WHT", Name = "Withholding Tax", CreatedAt = seedTimestamp },
new TaxType { TaxTypeId = 3, Code = "NBT", Name = "Nation Building Tax", CreatedAt = seedTimestamp },
new TaxType { TaxTypeId = 4, Code = "SSCL", Name = "Social Security Contribution Levy", CreatedAt = seedTimestamp },
new TaxType { TaxTypeId = 5, Code = "INCOME_TAX", Name = "Income Tax", CreatedAt = seedTimestamp }
);
Currency NonBaseCurrency(string code, string name, string? symbol, short decimalPlaces = 2) => new()
{
CurrencyCode = code,
CurrencyName = name,
Symbol = symbol,
DecimalPlaces = decimalPlaces,
IsBaseCurrency = false,
IsActive = true,
CreatedAt = seedTimestamp
};
modelBuilder.Entity<Currency>().HasData(
new Currency
{
CurrencyCode = "LKR",
CurrencyName = "Sri Lankan Rupee",
Symbol = "Rs",
DecimalPlaces = 2,
IsBaseCurrency = true,
IsActive = true,
CreatedAt = seedTimestamp
},
// Major / reserve currencies
NonBaseCurrency("USD", "US Dollar", "$"),
NonBaseCurrency("EUR", "Euro", "€"),
NonBaseCurrency("GBP", "British Pound", "£"),
NonBaseCurrency("JPY", "Japanese Yen", "¥", 0),
NonBaseCurrency("CHF", "Swiss Franc", "CHF"),
NonBaseCurrency("CAD", "Canadian Dollar", "$"),
NonBaseCurrency("AUD", "Australian Dollar", "$"),
NonBaseCurrency("NZD", "New Zealand Dollar", "$"),
NonBaseCurrency("CNY", "Chinese Yuan", "¥"),
NonBaseCurrency("HKD", "Hong Kong Dollar", "$"),
NonBaseCurrency("SGD", "Singapore Dollar", "$"),
// South Asia
NonBaseCurrency("INR", "Indian Rupee", "₹"),
NonBaseCurrency("PKR", "Pakistani Rupee", "Rs"),
NonBaseCurrency("BDT", "Bangladeshi Taka", "৳"),
NonBaseCurrency("NPR", "Nepalese Rupee", "Rs"),
NonBaseCurrency("BTN", "Bhutanese Ngultrum", "Nu."),
NonBaseCurrency("MVR", "Maldivian Rufiyaa", "Rf"),
NonBaseCurrency("AFN", "Afghan Afghani", "؋"),
// East / Southeast Asia
NonBaseCurrency("KRW", "South Korean Won", "₩", 0),
NonBaseCurrency("TWD", "New Taiwan Dollar", "NT$"),
NonBaseCurrency("MYR", "Malaysian Ringgit", "RM"),
NonBaseCurrency("THB", "Thai Baht", "฿"),
NonBaseCurrency("IDR", "Indonesian Rupiah", "Rp"),
NonBaseCurrency("PHP", "Philippine Peso", "₱"),
NonBaseCurrency("VND", "Vietnamese Dong", "₫", 0),
NonBaseCurrency("MMK", "Myanmar Kyat", "K"),
NonBaseCurrency("KHR", "Cambodian Riel", "៛"),
NonBaseCurrency("LAK", "Lao Kip", "₭"),
NonBaseCurrency("BND", "Brunei Dollar", "$"),
NonBaseCurrency("MOP", "Macanese Pataca", "MOP$"),
NonBaseCurrency("MNT", "Mongolian Tugrik", "₮"),
// Middle East
NonBaseCurrency("AED", "UAE Dirham", "د.إ"),
NonBaseCurrency("SAR", "Saudi Riyal", "﷼"),
NonBaseCurrency("QAR", "Qatari Riyal", "﷼"),
NonBaseCurrency("KWD", "Kuwaiti Dinar", "د.ك", 3),
NonBaseCurrency("BHD", "Bahraini Dinar", ".د.ب", 3),
NonBaseCurrency("OMR", "Omani Rial", "﷼", 3),
NonBaseCurrency("ILS", "Israeli New Shekel", "₪"),
NonBaseCurrency("JOD", "Jordanian Dinar", "د.ا", 3),
NonBaseCurrency("LBP", "Lebanese Pound", "ل.ل"),
NonBaseCurrency("IQD", "Iraqi Dinar", "ع.د", 3),
NonBaseCurrency("IRR", "Iranian Rial", "﷼"),
NonBaseCurrency("YER", "Yemeni Rial", "﷼"),
NonBaseCurrency("SYP", "Syrian Pound", "£"),
NonBaseCurrency("TRY", "Turkish Lira", "₺"),
// Africa
NonBaseCurrency("ZAR", "South African Rand", "R"),
NonBaseCurrency("EGP", "Egyptian Pound", "£"),
NonBaseCurrency("NGN", "Nigerian Naira", "₦"),
NonBaseCurrency("KES", "Kenyan Shilling", "KSh"),
NonBaseCurrency("GHS", "Ghanaian Cedi", "₵"),
NonBaseCurrency("TZS", "Tanzanian Shilling", "TSh"),
NonBaseCurrency("UGX", "Ugandan Shilling", "USh", 0),
NonBaseCurrency("ETB", "Ethiopian Birr", "Br"),
NonBaseCurrency("MAD", "Moroccan Dirham", "د.م."),
NonBaseCurrency("DZD", "Algerian Dinar", "د.ج"),
NonBaseCurrency("TND", "Tunisian Dinar", "د.ت", 3),
NonBaseCurrency("LYD", "Libyan Dinar", "ل.د", 3),
NonBaseCurrency("SDG", "Sudanese Pound", "ج.س."),
NonBaseCurrency("XOF", "West African CFA Franc", "CFA", 0),
NonBaseCurrency("XAF", "Central African CFA Franc", "FCFA", 0),
NonBaseCurrency("RWF", "Rwandan Franc", "FRw", 0),
NonBaseCurrency("BIF", "Burundian Franc", "FBu", 0),
NonBaseCurrency("DJF", "Djiboutian Franc", "Fdj", 0),
NonBaseCurrency("SOS", "Somali Shilling", "S"),
NonBaseCurrency("ZMW", "Zambian Kwacha", "ZK"),
NonBaseCurrency("MWK", "Malawian Kwacha", "MK"),
NonBaseCurrency("MZN", "Mozambican Metical", "MT"),
NonBaseCurrency("AOA", "Angolan Kwanza", "Kz"),
NonBaseCurrency("BWP", "Botswana Pula", "P"),
NonBaseCurrency("NAD", "Namibian Dollar", "$"),
NonBaseCurrency("SZL", "Eswatini Lilangeni", "L"),
NonBaseCurrency("LSL", "Lesotho Loti", "L"),
NonBaseCurrency("GMD", "Gambian Dalasi", "D"),
NonBaseCurrency("SLL", "Sierra Leonean Leone", "Le"),
NonBaseCurrency("LRD", "Liberian Dollar", "$"),
NonBaseCurrency("GNF", "Guinean Franc", "FG", 0),
NonBaseCurrency("CVE", "Cape Verdean Escudo", "$"),
NonBaseCurrency("MRU", "Mauritanian Ouguiya", "UM"),
NonBaseCurrency("KMF", "Comorian Franc", "CF", 0),
NonBaseCurrency("MGA", "Malagasy Ariary", "Ar"),
NonBaseCurrency("MUR", "Mauritian Rupee", "₨"),
NonBaseCurrency("SCR", "Seychellois Rupee", "₨"),
NonBaseCurrency("CDF", "Congolese Franc", "FC"),
NonBaseCurrency("XPF", "CFP Franc", "₣", 0),
// Europe (non-euro)
NonBaseCurrency("SEK", "Swedish Krona", "kr"),
NonBaseCurrency("NOK", "Norwegian Krone", "kr"),
NonBaseCurrency("DKK", "Danish Krone", "kr"),
NonBaseCurrency("PLN", "Polish Zloty", "zł"),
NonBaseCurrency("CZK", "Czech Koruna", "Kč"),
NonBaseCurrency("HUF", "Hungarian Forint", "Ft"),
NonBaseCurrency("RON", "Romanian Leu", "lei"),
NonBaseCurrency("BGN", "Bulgarian Lev", "лв"),
NonBaseCurrency("RSD", "Serbian Dinar", "дин."),
NonBaseCurrency("ISK", "Icelandic Krona", "kr", 0),
NonBaseCurrency("UAH", "Ukrainian Hryvnia", "₴"),
NonBaseCurrency("RUB", "Russian Ruble", "₽"),
NonBaseCurrency("BYN", "Belarusian Ruble", "Br"),
NonBaseCurrency("MDL", "Moldovan Leu", "L"),
NonBaseCurrency("ALL", "Albanian Lek", "L"),
NonBaseCurrency("MKD", "Macedonian Denar", "ден"),
NonBaseCurrency("BAM", "Bosnia-Herzegovina Convertible Mark", "KM"),
NonBaseCurrency("GEL", "Georgian Lari", "₾"),
NonBaseCurrency("AMD", "Armenian Dram", "֏"),
NonBaseCurrency("AZN", "Azerbaijani Manat", "₼"),
// Central Asia
NonBaseCurrency("KZT", "Kazakhstani Tenge", "₸"),
NonBaseCurrency("UZS", "Uzbekistani Som", null),
NonBaseCurrency("KGS", "Kyrgystani Som", null),
NonBaseCurrency("TJS", "Tajikistani Somoni", "ЅМ"),
NonBaseCurrency("TMT", "Turkmenistani Manat", "m"),
// Americas
NonBaseCurrency("BRL", "Brazilian Real", "R$"),
NonBaseCurrency("MXN", "Mexican Peso", "$"),
NonBaseCurrency("ARS", "Argentine Peso", "$"),
NonBaseCurrency("CLP", "Chilean Peso", "$", 0),
NonBaseCurrency("COP", "Colombian Peso", "$"),
NonBaseCurrency("PEN", "Peruvian Sol", "S/"),
NonBaseCurrency("UYU", "Uruguayan Peso", "$U"),
NonBaseCurrency("BOB", "Bolivian Boliviano", "Bs."),
NonBaseCurrency("PYG", "Paraguayan Guarani", "₲", 0),
NonBaseCurrency("VES", "Venezuelan Bolivar", "Bs."),
NonBaseCurrency("GYD", "Guyanese Dollar", "$"),
NonBaseCurrency("SRD", "Surinamese Dollar", "$"),
NonBaseCurrency("CRC", "Costa Rican Colon", "₡"),
NonBaseCurrency("GTQ", "Guatemalan Quetzal", "Q"),
NonBaseCurrency("HNL", "Honduran Lempira", "L"),
NonBaseCurrency("NIO", "Nicaraguan Cordoba", "C$"),
NonBaseCurrency("PAB", "Panamanian Balboa", "B/."),
NonBaseCurrency("DOP", "Dominican Peso", "RD$"),
NonBaseCurrency("JMD", "Jamaican Dollar", "$"),
NonBaseCurrency("TTD", "Trinidad and Tobago Dollar", "$"),
NonBaseCurrency("BBD", "Barbadian Dollar", "$"),
NonBaseCurrency("BSD", "Bahamian Dollar", "$"),
NonBaseCurrency("BZD", "Belize Dollar", "$"),
NonBaseCurrency("HTG", "Haitian Gourde", "G"),
NonBaseCurrency("CUP", "Cuban Peso", "$"),
NonBaseCurrency("XCD", "East Caribbean Dollar", "$"),
// Oceania
NonBaseCurrency("FJD", "Fijian Dollar", "$"),
NonBaseCurrency("PGK", "Papua New Guinean Kina", "K"),
NonBaseCurrency("WST", "Samoan Tala", "T"),
NonBaseCurrency("TOP", "Tongan Pa'anga", "T$"),
NonBaseCurrency("VUV", "Vanuatu Vatu", "VT", 0),
NonBaseCurrency("SBD", "Solomon Islands Dollar", "$")
);
}
}
}