Add GL account configuration and role mapping entities

- Introduced GlAccountConfig entity for mapping accounting roles to GL account codes.
- Added GlAccountConfigConfiguration for database configuration of the singleton GL account mapping.
- Created GlAccountRole enum to define accounting roles used in GL postings.
- Enhanced error handling with new error codes for unconfigured and invalid GL accounts.
- Updated existing DbContext to include GlAccountConfig DbSet.
This commit is contained in:
2026-10-07 12:51:15 +05:30
parent 65976c5cd3
commit 614df3065d
7 changed files with 226 additions and 3 deletions
@@ -0,0 +1,51 @@
using ERPCore.Domain.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace ERPCore.Infra.Persistence.Configurations;
/// <summary>
/// Configures the singleton GL account-mapping row. The check constraint is what makes
/// "singleton" a database guarantee rather than a convention (mirrors <see cref="GrnConfigConfiguration"/>).
/// Backtick quoting is deliberate - MariaDB rejects the double-quoted form, which crash-looped
/// startup seeding once already (c97a7e9).
/// </summary>
public sealed class GlAccountConfigConfiguration : IEntityTypeConfiguration<GlAccountConfig>
{
public void Configure(EntityTypeBuilder<GlAccountConfig> builder)
{
builder.ToTable("gl_account_config", t =>
t.HasCheckConstraint("ck_gl_account_config_singleton", $"`ConfigId` = {GlAccountConfig.SingletonId}"));
builder.HasKey(c => c.ConfigId);
// The id is fixed, never generated — there is exactly one row, seeded by DataSeeder.
builder.Property(c => c.ConfigId).ValueGeneratedNever();
foreach (var code in new[]
{
nameof(GlAccountConfig.InventoryAccountCode),
nameof(GlAccountConfig.VatRecoverableAccountCode),
nameof(GlAccountConfig.AccountsReceivableCode),
nameof(GlAccountConfig.ClearingAccountCode),
nameof(GlAccountConfig.TaxPayableAccountCode),
nameof(GlAccountConfig.AccountsPayableCode),
nameof(GlAccountConfig.SalesRevenueAccountCode),
nameof(GlAccountConfig.SalesDiscountAccountCode),
nameof(GlAccountConfig.InventoryGainAccountCode),
nameof(GlAccountConfig.CogsAccountCode),
nameof(GlAccountConfig.InventoryLossAccountCode),
nameof(GlAccountConfig.CashAccountCode)
})
{
builder.Property(code).HasMaxLength(GlAccountConfig.CodeMaxLength);
}
// RowVersion is made a concurrency token by the model-wide convention in ErpDbContext.
builder.HasOne(c => c.UpdatedByUser)
.WithMany()
.HasForeignKey(c => c.UpdatedBy)
.OnDelete(DeleteBehavior.Restrict);
}
}
@@ -48,6 +48,8 @@ public class ErpDbContext : DbContext
public DbSet<BranchConfig> BranchConfig => Set<BranchConfig>();
public DbSet<SalesConfig> SalesConfig => Set<SalesConfig>();
public DbSet<GrnConfig> GrnConfig => Set<GrnConfig>();
/// <summary>Singleton row mapping each accounting role to a GL account code.</summary>
public DbSet<GlAccountConfig> GlAccountConfig => Set<GlAccountConfig>();
/// <summary>Singleton row for notification delivery preferences.</summary>
public DbSet<NotificationConfig> NotificationConfig => Set<NotificationConfig>();
public DbSet<NotificationConfigModule> NotificationConfigModules => Set<NotificationConfigModule>();