1170 lines
57 KiB
C#
1170 lines
57 KiB
C#
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", "$")
|
||
);
|
||
}
|
||
}
|
||
}
|