General Ledger Domain Documentation
Overview
This section contains domain documentation for the General Ledger sub-module, which serves as the core accounting foundation with multi-dimensional transaction processing capabilities. The domain layer represents pure business logic independent of infrastructure concerns.
Core Aggregates
Ledger Aggregate Complete
Purpose: Central configuration and management of accounting ledgers.
Key Responsibilities:
- Ledger configuration and setup
- Chart of accounts management
- Fiscal calendar integration
- Currency and exchange rate handling
- Account structure assignment and validation
Business Rules:
- Each ledger must have exactly one chart of accounts
- Fiscal periods must be sequential and non-overlapping
- Account structures must not have overlapping MainAccount constraints
- Only one reporting currency per ledger
Domain Events:
LedgerCreatedDomainEventAccountStructureAddedToLedgerDomainEventFiscalPeriodOpenedDomainEventFiscalPeriodClosedDomainEvent
Key Methods:
public void AddAccountStructure(DimensionHierarchy accountStructure)
public void OpenFiscalPeriod(FiscalPeriod period)
public void CloseFiscalPeriod(Guid periodId, DateTime closingDate)
public bool IsValidTransactionDate(DateTime transactionDate)
DimensionHierarchy Aggregate Complete
Purpose: Defines account structures (dimensional hierarchies) for financial classification.
Key Responsibilities:
- Account structure definition and management
- Dimension level ordering and constraints
- MainAccount constraint tree management
- Activation/deactivation lifecycle
Business Rules:
- Level ordering must be sequential (1, 2, 3, ...)
- MainAccount constraint ranges cannot overlap within a ledger
- Mandatory levels must be filled for valid combinations
- Cannot be deleted if referenced by existing combinations
- Activation requires constraint overlap validation
Domain Events:
DimensionHierarchyCreatedDomainEventDimensionHierarchyActivatedDomainEventDimensionHierarchyDeactivatedDomainEventConstraintOverlapDetectedDomainEvent
Key Methods:
public void Activate(IConstraintOverlapDetectionService overlapService, Ledger ledger)
public void AddLevel(int level, Guid dimensionAttributeId, bool isMandatory)
public void AddConstraint(ConstraintNode constraintNode)
public bool MatchesMainAccount(string mainAccountValue)
DimensionCombination Aggregate Complete
Purpose: Immutable combinations of financial dimension values representing complete account classifications.
Key Responsibilities:
- Unique dimension value combinations
- Display value generation and caching
- Hash-based identity and deduplication
- Relationship to account structures
Business Rules:
- Once created, combinations are immutable
- Hash-based uniqueness prevents duplicates
- All mandatory dimensions must be provided
- Values must exist and be active in their respective dimensions
- Display values automatically generated for UI presentation
Domain Events:
DimensionCombinationCreatedDomainEventDimensionCombinationReusedDomainEvent
Key Methods:
public static DimensionCombination Create(
IReadOnlyList<DimensionSegment> segments,
DimensionHierarchy accountStructure,
LedgerDimensionType dimensionType)
public string GenerateDisplayValue()
public bool IsValidForAccountStructure(DimensionHierarchy accountStructure)
LedgerJournalHeader Aggregate Pending Documentation
Purpose: Journal management with complete posting lifecycle.
Key Responsibilities:
- Journal creation and line management
- Posting validation and execution
- Reversal journal creation
- Balance validation (debits = credits)
- Integration with fiscal periods
Business Rules:
- Journals must balance before posting
- Cannot post to closed fiscal periods
- Reversals create new journals with opposite amounts
- Posted journals cannot be modified
- All transactions must have valid dimension combinations
Planned Domain Events:
LedgerJournalCreatedDomainEventLedgerJournalLineAddedDomainEventLedgerJournalPostedDomainEventLedgerJournalReversedDomainEvent
LedgerJournalLine Entity Pending Documentation
Purpose: Individual transaction lines within journals.
Key Responsibilities:
- Transaction amount and description management
- Dimension combination linkage
- Currency handling
- Date validation
Business Rules:
- Either debit or credit amount (not both)
- Must reference valid dimension combination
- Transaction date must be within journal's allowed range
- Cannot be modified after journal posting
Domain Services
AccountStructureResolutionService Complete
Purpose: Resolves the appropriate account structure based on MainAccount value.
Key Operations:
Task<AccountStructureResolutionResult> ResolveAccountStructureAsync(
string mainAccountValue,
Ledger ledger)
Business Logic:
- Searches all active account structures assigned to ledger
- Matches MainAccount value against constraint trees
- Prevents ambiguous matches (Golden Rule enforcement)
- Returns success/failure with detailed messages
Integration Points:
- Used by journal entry processes
- Called from dimension resolution APIs
- Validates account structure assignments
ConstraintOverlapDetectionService Complete
Purpose: Prevents MainAccount constraint overlaps that would cause ambiguity.
Key Operations:
Task<OverlapDetectionResult> DetectOverlapsAsync(
DimensionHierarchy dimensionHierarchy,
Ledger ledger)
Business Logic:
- Critical overlaps prevent account structure activation
- Warning overlaps generate notifications but allow activation
- Uses constraint type logic from ConstraintType entity
- Analyzes constraint trees for all possible conflicts
Enforcement Points:
- DimensionHierarchy activation
- Adding DimensionHierarchy to Ledger
- MainAccount constraint modification
DimensionCombinationDomainService Complete
Purpose: Creates and manages dimension combinations with deduplication.
Key Operations:
Task<DimensionCombination> CreateOrReuseCombinationAsync(
IReadOnlyList<DimensionSegment> segments,
DimensionHierarchy accountStructure,
LedgerDimensionType dimensionType)
Business Logic:
- Hash-based deduplication ensures reuse of identical combinations
- Validates completeness against mandatory dimension levels
- Generates display values for UI presentation
- Maintains referential integrity for posted transactions
Performance Considerations:
- Hash computation optimized for quick lookups
- Display value generation cached
- Reuse patterns minimize storage overhead
JournalPostingDomainService Planned
Purpose: Orchestrates journal posting with all validations.
Planned Operations:
Task<PostingResult> PostJournalAsync(LedgerJournalHeader journal, DateTime postingDate)
Task<LedgerJournalHeader> ReverseJournalAsync(LedgerJournalHeader originalJournal, string reason)
Planned Business Logic:
- Balance validation (debits = credits)
- Fiscal period validation
- Dimension combination validation
- General ledger entry creation
- Audit trail generation
Value Objects
DimensionSegment
Purpose: Represents a single dimension value within a combination.
public record DimensionSegment(
Guid DimensionAttributeId,
Guid DimensionAttributeValueId,
int Level)
{
public bool IsValid => DimensionAttributeId != Guid.Empty &&
DimensionAttributeValueId != Guid.Empty &&
Level > 0;
}
ConstraintNode
Purpose: Represents a node in the MainAccount constraint tree.
public record ConstraintNode(
ConstraintType ConstraintType,
string Value,
IReadOnlyList<ConstraintNode> Children)
{
public bool Matches(string mainAccountValue) => ConstraintType.Evaluate(Value, mainAccountValue);
}
Money
Purpose: Ensures currency consistency across operations.
public record Money(decimal Amount, string CurrencyCode)
{
public Money Add(Money other)
{
if (CurrencyCode != other.CurrencyCode)
throw new InvalidOperationException("Cannot add amounts in different currencies");
return new Money(Amount + other.Amount, CurrencyCode);
}
}
AccountingPeriod
Purpose: Represents fiscal periods with status management.
public record AccountingPeriod(
DateTime StartDate,
DateTime EndDate,
PeriodStatus Status)
{
public bool IsOpen => Status == PeriodStatus.Open;
public bool Contains(DateTime date) => date >= StartDate && date <= EndDate;
}
Key Business Rules
Golden Rule: MainAccount Uniqueness
Rule: A single MainAccount value cannot exist in more than one active DimensionHierarchy assigned to the same Ledger.
Rationale: Prevents ambiguous account structure resolution during transaction entry.
Enforcement Points:
- DimensionHierarchy activation via
ConstraintOverlapDetectionService - Adding DimensionHierarchy to Ledger
- MainAccount constraint modification
Implementation:
public async Task<OverlapDetectionResult> DetectOverlapsAsync(
DimensionHierarchy candidateStructure,
Ledger ledger)
{
var existingStructures = ledger.GetActiveAccountStructures();
var overlaps = new List<ConstraintOverlap>();
foreach (var existing in existingStructures)
{
var overlap = DetectConstraintOverlap(candidateStructure, existing);
if (overlap.IsCritical)
{
overlaps.Add(overlap);
}
}
return new OverlapDetectionResult(overlaps);
}
Dimension Combination Immutability
Rule: Once created, DimensionCombination entities cannot be modified.
Rationale:
- Ensures referential integrity for posted transactions
- Enables safe reuse of combinations
- Prevents historical data corruption
Implementation: Private setters and no update methods
Journal Balance Requirement
Rule: Journal total debits must equal total credits before posting.
Enforcement:
public PostingValidationResult ValidateForPosting()
{
var totalDebits = Lines.Sum(l => l.DebitAmount);
var totalCredits = Lines.Sum(l => l.CreditAmount);
if (totalDebits != totalCredits)
{
return PostingValidationResult.Failure("Journal is not balanced");
}
return PostingValidationResult.Success();
}
Fiscal Period Validation
Rule: Transactions can only be posted to open fiscal periods.
Enforcement:
public bool CanPostTransaction(DateTime transactionDate)
{
var period = FiscalCalendar.GetPeriodForDate(transactionDate);
return period?.IsOpen == true;
}
Domain Event Flows
Journal Posting Workflow
Dimension Combination Creation
Integration Points
With Financial Dimensions Module
// GL depends on FD for dimension value resolution
public class LedgerJournalLine
{
public async Task SetDimensionCombinationAsync(
IList<DimensionSegmentInput> segmentInputs,
IDimensionValueResolverService dimensionResolver,
IDimensionCombinationDomainService combinationService)
{
// 1. Resolve segment values using FD service
var resolvedSegments = await dimensionResolver
.ResolveSegmentValuesAsync(segmentInputs);
// 2. Create or reuse combination using GL service
var combination = await combinationService
.CreateOrReuseCombinationAsync(resolvedSegments, accountStructure);
// 3. Link to journal line
LedgerDimensionId = combination.Id;
}
}
With Accounts Receivable Module (Future)
// AR invoices inherit customer default dimensions
public class CustomerInvoicePostingDomainService
{
public async Task<LedgerJournalHeader> CreateJournalFromInvoiceAsync(
CustomerInvoice invoice)
{
var customer = await _customerRepository.GetByIdAsync(invoice.CustomerId);
var defaultDimensions = await _dimensionService
.GetDimensionSetAsync(customer.DefaultDimensionId);
var journal = new LedgerJournalHeader(/* parameters */);
foreach (var invoiceLine in invoice.Lines)
{
var inheritedDimensions = MergeDimensions(
defaultDimensions,
invoiceLine.OverrideDimensions);
var journalLine = new LedgerJournalLine(/* parameters */);
await journalLine.SetDimensionCombinationAsync(inheritedDimensions, /*services*/);
journal.AddLine(journalLine);
}
return journal;
}
}
Testing Patterns
Aggregate Testing
[TestFixture]
public class LedgerJournalHeaderTests
{
[Test]
public void Post_Should_Raise_Domain_Event_When_Valid()
{
// Arrange
var journal = CreateBalancedJournal();
var mockValidator = new Mock<IJournalValidator>();
mockValidator.Setup(v => v.ValidateForPosting(journal))
.Returns(PostingValidationResult.Success());
// Act
journal.Post(DateTime.Now, mockValidator.Object);
// Assert
var domainEvent = journal.GetDomainEvents()
.OfType<LedgerJournalPostedDomainEvent>().First();
Assert.IsNotNull(domainEvent);
Assert.AreEqual(JournalStatus.Posted, journal.Status);
}
[Test]
public void Post_Should_Throw_When_Unbalanced()
{
// Arrange
var journal = CreateUnbalancedJournal();
var mockValidator = new Mock<IJournalValidator>();
mockValidator.Setup(v => v.ValidateForPosting(journal))
.Returns(PostingValidationResult.Failure("Unbalanced"));
// Act & Assert
Assert.Throws<JournalPostingException>(
() => journal.Post(DateTime.Now, mockValidator.Object));
}
}
Domain Service Testing
[TestFixture]
public class AccountStructureResolutionServiceTests
{
[Test]
public async Task Should_Resolve_Correct_Structure_For_Assets()
{
// Arrange
var service = new AccountStructureResolutionService();
var ledger = CreateLedgerWithAssetStructure(); // 1000-1999 range
// Act
var result = await service.ResolveAccountStructureAsync("1100", ledger);
// Assert
Assert.IsTrue(result.IsSuccess);
Assert.AreEqual("Assets Account Structure", result.ResolvedStructure.Name);
}
[Test]
public async Task Should_Fail_When_No_Structure_Matches()
{
// Arrange
var service = new AccountStructureResolutionService();
var ledger = CreateLedgerWithAssetStructure(); // 1000-1999 range
// Act
var result = await service.ResolveAccountStructureAsync("9999", ledger);
// Assert
Assert.IsFalse(result.IsSuccess);
Assert.Contains("No matching account structure", result.Message);
}
}
Performance Considerations
Aggregate Size Management
- LedgerJournalHeader: Use lazy loading for large journals with many lines
- DimensionCombination: Lightweight and cacheable, optimized for frequent access
- DimensionHierarchy: Cache resolved structures to avoid repeated constraint evaluation
Event Processing
- Domain events processed synchronously within transaction boundaries
- Integration events processed asynchronously via message bus
- Avoid heavy operations in domain event handlers
Memory Optimization
// Efficient dimension combination lookup
public class CachedDimensionCombinationService
{
private readonly IMemoryCache _cache;
public async Task<DimensionCombination> GetOrCreateAsync(
string combinationHash,
Func<Task<DimensionCombination>> factory)
{
return await _cache.GetOrCreateAsync(
$"combination:{combinationHash}",
async entry =>
{
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(4);
return await factory();
});
}
}
Future Enhancements
Planned Domain Features
- Advanced Approval Workflows: Configurable journal approval processes
- Multi-Currency Enhancements: Enhanced foreign currency handling
- Intercompany Transactions: Cross-company transaction processing
- Advanced Audit Capabilities: Enhanced audit trail and change tracking
Domain Model Evolution
- Event Sourcing: Consider for complete audit requirements
- Microservices Split: Potential bounded context evolution
- Performance Optimization: Aggregate optimization for high-volume scenarios
Related Documentation
Implementation References
- General Ledger API Documentation - REST endpoints exposing domain operations
- Journal Entry Concepts - Complete posting workflow
- Dimension Resolution Concepts - Dimensional accounting workflow
Cross-Module References
- Financial Dimensions Domain - Shared dimension infrastructure
- Finance Domain Overview - Complete finance module domain documentation
Last Updated: [Current Date] | Version: 1.0 | Status: Active Development
Priority Items:
- Complete LedgerJournalHeader aggregate documentation
- Implement JournalPostingDomainService
- Add comprehensive integration testing
- Performance optimization for high-volume scenarios