Skip to main content

Financial Dimensions Domain Documentation

Overview

This section contains domain documentation for the Financial Dimensions sub-module, which provides shared dimensional infrastructure used across all finance modules. This foundational module enables multi-dimensional accounting by managing dimension attributes, values, and default dimension sets.

Core Aggregates

DimensionAttribute Aggregate Pending Documentation

Purpose: Defines dimension types and their metadata for use across finance modules.

Key Responsibilities:

  • Dimension attribute definition and lifecycle management
  • Type-specific validation rules (CustomList, DynamicEntity, FinancialDimension)
  • Activation/deactivation workflow
  • Integration with backing entity systems

Business Rules:

  • Dimension names must be unique within the system
  • Cannot be deleted if referenced by existing combinations
  • Custom dimensions require at least one active value
  • Entity-backed dimensions must specify valid backing entity configuration
  • Financial dimensions (MainAccount) are system-required and cannot be deactivated

Planned Domain Events:

  • DimensionAttributeCreatedDomainEvent
  • DimensionAttributeActivatedDomainEvent
  • DimensionAttributeDeactivatedDomainEvent
  • DimensionAttributeValueAddedDomainEvent

Planned Key Methods:

public void Activate(IDimensionValidationService validationService)
public void Deactivate()
public void AddValue(DimensionAttributeValue value)
public bool CanBeDeleted()
public ValidationResult ValidateValue(string value)

DimensionAttributeValue Aggregate Pending Documentation

Purpose: Individual dimension values with lifecycle management and validation.

Key Responsibilities:

  • Dimension value creation and management
  • Suspension/activation lifecycle
  • Group dimension support for reporting
  • Integration with backing entity data sources

Business Rules:

  • Values must be unique within their dimension attribute
  • Suspended values cannot be used in new transactions
  • Cannot be deleted if referenced by existing combinations
  • Display values automatically generated for entity-backed dimensions
  • Group dimensions enable hierarchical reporting

Planned Domain Events:

  • DimensionAttributeValueCreatedDomainEvent
  • DimensionAttributeValueSuspendedDomainEvent
  • DimensionAttributeValueActivatedDomainEvent
  • DimensionAttributeValueUpdatedDomainEvent

Planned Key Methods:

public void Suspend(string reason)
public void Activate()
public void UpdateDisplayValue(string newDisplayValue)
public bool CanBeDeleted()

DimensionAttributeValueSet Aggregate Pending Documentation

Purpose: Collections of default dimension values for master data integration.

Key Responsibilities:

  • Default dimension set creation and management
  • Hash-based uniqueness for efficient reuse
  • Master data integration (Customer, Vendor, etc.)
  • Cross-module dimension inheritance

Business Rules:

  • Hash-based identity ensures identical sets are reused
  • Cannot be modified once created (create new set instead)
  • Must contain only valid, active dimension values
  • Used for inheriting dimensions from master data to transactions

Planned Domain Events:

  • DimensionAttributeValueSetCreatedDomainEvent
  • DimensionAttributeValueSetReusedDomainEvent
  • DimensionAttributeValueSetAssignedDomainEvent

Planned Key Methods:

public static DimensionAttributeValueSet Create(
IReadOnlyList<DimensionAttributeValueSetItem> items,
IDimensionAttributeValueSetDomainService domainService)
public string GenerateHash()
public bool IsEquivalentTo(DimensionAttributeValueSet other)

DimensionAttributeValueSetItem Entity Pending Documentation

Purpose: Individual items within a dimension value set.

Key Responsibilities:

  • Links specific dimension values to value sets
  • Maintains referential integrity
  • Provides efficient querying capabilities

Business Rules:

  • Must reference valid, active dimension attribute and value
  • Cannot have duplicate dimension attributes within the same set
  • Immutable once created

Domain Services

DimensionValueResolverService Complete

Purpose: Converts string dimension values to stable IDs using get-or-create pattern.

Key Operations:

Task<IReadOnlyList<DimensionSegment>> ResolveSegmentValuesAsync(
IList<DimensionSegmentInput> segmentInputs,
CancellationToken cancellationToken = default)

Business Logic:

  • Validates dimension attribute exists and is active
  • For CustomList dimensions: finds existing value or creates new one
  • For DynamicEntity dimensions: validates value exists in backing entity
  • For FinancialDimension dimensions: validates against chart of accounts
  • Returns stable IDs for use in dimension combinations

Error Handling:

  • Throws if dimension attribute not found or inactive
  • Throws if value is suspended and cannot be used
  • Throws if backing entity validation fails

DimensionAttributeValueSetDomainService Planned

Purpose: Manages dimension value set creation with hash-based deduplication.

Planned Operations:

Task<DimensionAttributeValueSet> GetOrCreateAsync(
IList<DimensionSegmentInput> segmentInputs,
string description = null)
Task<string> GenerateHashAsync(IReadOnlyList<(Guid DimensionAttributeId, Guid ValueId)> pairs)
Task<DimensionAttributeValueSet> FindByHashAsync(string hash)

Planned Business Logic:

  • Resolves input values to stable IDs
  • Calculates hash based on sorted (DimensionAttributeId, ValueId) pairs
  • Checks for existing set with same hash
  • Creates new set only if no existing match found
  • Updates usage statistics for analytics

DimensionValidationService Planned

Purpose: Cross-cutting validation logic for dimensional consistency.

Planned Operations:

Task<ValidationResult> ValidateDimensionValueAsync(
Guid dimensionAttributeId,
string value)
Task<ValidationResult> ValidateDimensionCombinationAsync(
IList<DimensionSegmentInput> segments,
DimensionHierarchy accountStructure)
Task<bool> IsValueActiveAsync(Guid dimensionAttributeId, Guid valueId)

Planned Business Logic:

  • Validates individual dimension values
  • Checks cross-dimensional business rules
  • Validates against account structure requirements
  • Provides detailed validation messages for UI feedback

DynamicDimensionValueProviderRegistry Planned

Purpose: Registry for entity-backed dimension value providers.

Planned Operations:

void RegisterProvider(string backingEntityKey, IDynamicDimensionValueProvider provider)
Task<IEnumerable<DimensionAttributeValueViewModel>> GetValuesAsync(
DimensionAttributeMetadata metadata,
DimensionValueSearchContext searchContext)
Task<DimensionAttributeValueViewModel> GetValueByKeyAsync(
DimensionAttributeMetadata metadata,
string key)

Planned Business Logic:

  • Routes dimension value requests to appropriate providers
  • Handles caching and performance optimization
  • Manages provider lifecycle and error handling
  • Enables consistent interface for entity-backed dimensions

Value Objects

DimensionSegmentInput

Purpose: User input for dimension values during interactive entry.

public record DimensionSegmentInput(
Guid DimensionAttributeId,
string Value)
{
public bool IsValid => DimensionAttributeId != Guid.Empty && !string.IsNullOrWhiteSpace(Value);
}

DimensionAttributeMetadata

Purpose: Configuration metadata for entity-backed dimensions.

public record DimensionAttributeMetadata(
string BackingEntityKey,
string ValueAttribute,
string NameAttribute,
string KeyAttribute,
string ViewName = null)
{
public bool IsValid => !string.IsNullOrWhiteSpace(BackingEntityKey) &&
!string.IsNullOrWhiteSpace(ValueAttribute) &&
!string.IsNullOrWhiteSpace(KeyAttribute);
}

DimensionValueSearchContext

Purpose: Search and filtering context for dimension value lookups.

public record DimensionValueSearchContext(
string SearchTerm = null,
int? Limit = null,
int? Skip = null,
bool ExcludeSuspended = true,
IReadOnlyDictionary<Guid, string> ContextualFilters = null)
{
public bool HasSearchCriteria => !string.IsNullOrWhiteSpace(SearchTerm) ||
ContextualFilters?.Any() == true;
}

Key Business Rules

Dimension Attribute Uniqueness

Rule: Dimension attribute names must be unique across the entire system.

Rationale: Prevents confusion and ensures consistent dimensional classification.

Enforcement:

public class DimensionAttribute
{
public static DimensionAttribute Create(
string name,
IDimensionAttributeRepository repository)
{
if (await repository.ExistsByNameAsync(name))
{
throw new DomainException($"Dimension attribute with name '{name}' already exists");
}

return new DimensionAttribute(name);
}
}

Value Set Immutability

Rule: DimensionAttributeValueSet entities cannot be modified once created.

Rationale:

  • Enables safe reuse across multiple master data records
  • Maintains referential integrity for historical data
  • Supports hash-based deduplication

Implementation: Create new value set for any changes

Dimension Value Suspension Rules

Rule: Suspended dimension values cannot be used in new transactions but remain visible in historical data.

Enforcement:

public void ValidateValueForNewTransaction(DimensionAttributeValue value)
{
if (value.IsSuspended)
{
throw new DomainException(
$"Suspended dimension value '{value.Value}' cannot be used in new transactions");
}
}

Hash-Based Deduplication

Rule: Dimension value sets with identical content share the same hash and are reused.

Implementation:

public string GenerateHash()
{
var sortedPairs = Items
.Select(item => $"{item.DimensionAttributeId}:{item.DimensionAttributeValueId}")
.OrderBy(pair => pair)
.ToArray();

var concatenated = string.Join("|", sortedPairs);
return ComputeSHA256Hash(concatenated);
}

Cross-Module Integration Patterns

With General Ledger Module

// GL requests dimension resolution from FD
public class LedgerJournalTransactionHandler
{
private readonly IDimensionValueResolverService _dimensionResolver;

public async Task<LedgerJournalLine> CreateTransactionLineAsync(
AddLedgerJournalTransactionCommand command)
{
// 1. Resolve dimension values using FD service
var resolvedSegments = await _dimensionResolver
.ResolveSegmentValuesAsync(command.DimensionSegments);

// 2. GL creates dimension combination using resolved segments
var combination = await _dimensionCombinationService
.CreateOrReuseCombinationAsync(resolvedSegments, accountStructure);

// 3. Create journal line with stable dimension combination ID
return new LedgerJournalLine(
combination.Id,
command.Amount,
command.Description);
}
}

With Accounts Receivable Module (Future)

// Customer setup with default dimensions
public class Customer
{
public Guid? DefaultDimensionId { get; private set; }

public async Task SetDefaultDimensionsAsync(
IList<DimensionSegmentInput> dimensionInputs,
IDimensionAttributeValueSetDomainService valueSetService)
{
if (dimensionInputs?.Any() == true)
{
var valueSet = await valueSetService.GetOrCreateAsync(
dimensionInputs,
$"Default dimensions for customer {Name}");

DefaultDimensionId = valueSet.Id;
}
else
{
DefaultDimensionId = null;
}

AddDomainEvent(new CustomerDefaultDimensionsChangedDomainEvent(this));
}
}

// Invoice inherits customer default dimensions
public class CustomerInvoice
{
public async Task InheritCustomerDimensionsAsync(
Customer customer,
IDimensionAttributeValueSetService valueSetService)
{
if (customer.DefaultDimensionId.HasValue)
{
var defaultSet = await valueSetService
.GetByIdAsync(customer.DefaultDimensionId.Value);

// Apply default dimensions to invoice lines
foreach (var line in Lines.Where(l => !l.HasOverrideDimensions))
{
line.ApplyDimensionSet(defaultSet);
}
}
}
}

With CRM Module (Future)

// Customer dimension provider for entity-backed dimensions
public class CustomerDimensionValueProvider : IDynamicDimensionValueProvider
{
public string BackingEntityTypeKey => "customer";

public async Task<IEnumerable<DimensionAttributeValueViewModel>> GetValuesAsync(
DimensionAttributeMetadata metadata,
DimensionValueSearchContext searchContext,
CancellationToken cancellationToken)
{
var customers = await _customerRepository.SearchAsync(
searchContext.SearchTerm,
searchContext.Limit ?? 50,
cancellationToken);

return customers.Select(customer => new DimensionAttributeValueViewModel
{
Id = customer.Id,
Value = customer.CustomerNumber,
DisplayValue = customer.Name,
IsSuspended = !customer.IsActive,
GroupDimension = customer.CustomerCategory
});
}
}

Domain Event Flows

Dimension Value Set Creation

Dynamic Dimension Value Resolution


Performance Considerations

Caching Strategies

// Dimension metadata caching
public class CachedDimensionAttributeService
{
private readonly IMemoryCache _cache;

public async Task<DimensionAttribute> GetByIdAsync(Guid id)
{
return await _cache.GetOrCreateAsync(
$"dimension-attribute:{id}",
async entry =>
{
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(24);
return await _repository.GetByIdAsync(id);
});
}
}

// Value lookup caching for dynamic entities
public class CachedDynamicDimensionValueProvider
{
private readonly IMemoryCache _cache;

public async Task<DimensionAttributeValueViewModel> GetValueByKeyAsync(
DimensionAttributeMetadata metadata,
string key)
{
var cacheKey = $"dynamic-value:{metadata.BackingEntityKey}:{key}";

return await _cache.GetOrCreateAsync(cacheKey, async entry =>
{
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(15);
return await _baseProvider.GetValueByKeyAsync(metadata, key);
});
}
}

Bulk Operations Optimization

// Efficient bulk dimension value creation
public class BulkDimensionValueService
{
public async Task<BulkOperationResult> CreateValuesAsync(
Guid dimensionAttributeId,
IEnumerable<DimensionValueCreateInput> values)
{
var batches = values.Chunk(100); // Process in batches
var results = new List<DimensionAttributeValue>();

foreach (var batch in batches)
{
var batchResults = await ProcessBatchAsync(dimensionAttributeId, batch);
results.AddRange(batchResults);
}

return new BulkOperationResult(results);
}
}

Hash Computation Optimization

// Optimized hash generation for dimension value sets
public class OptimizedHashGenerator
{
private static readonly SHA256 _hasher = SHA256.Create();

public string GenerateHash(IReadOnlyList<(Guid AttributeId, Guid ValueId)> pairs)
{
// Pre-sort to ensure consistent hash regardless of input order
var sortedPairs = pairs.OrderBy(p => p.AttributeId).ThenBy(p => p.ValueId);

using var stream = new MemoryStream();
using var writer = new BinaryWriter(stream);

foreach (var (attributeId, valueId) in sortedPairs)
{
writer.Write(attributeId.ToByteArray());
writer.Write(valueId.ToByteArray());
}

var hashBytes = _hasher.ComputeHash(stream.ToArray());
return Convert.ToBase64String(hashBytes);
}
}

Testing Strategies

Aggregate Testing

[TestFixture]
public class DimensionAttributeValueSetTests
{
[Test]
public void Create_Should_Generate_Consistent_Hash()
{
// Arrange
var items = new[]
{
new DimensionAttributeValueSetItem(attrId1, valueId1),
new DimensionAttributeValueSetItem(attrId2, valueId2)
};

// Act
var set1 = DimensionAttributeValueSet.Create(items, mockService.Object);
var set2 = DimensionAttributeValueSet.Create(items.Reverse(), mockService.Object);

// Assert
Assert.AreEqual(set1.Hash, set2.Hash);
}

[Test]
public void Create_Should_Validate_All_Items()
{
// Arrange
var invalidItems = new[]
{
new DimensionAttributeValueSetItem(Guid.Empty, valueId1)
};

// Act & Assert
Assert.Throws<DomainException>(
() => DimensionAttributeValueSet.Create(invalidItems, mockService.Object));
}
}

Domain Service Testing

[TestFixture]
public class DimensionValueResolverServiceTests
{
[Test]
public async Task Should_Create_New_Value_When_Not_Exists()
{
// Arrange
var service = new DimensionValueResolverService(mockRepo.Object, mockProviders.Object);
var input = new DimensionSegmentInput(customListDimId, "NEW_VALUE");

mockRepo.Setup(r => r.FindValueAsync(customListDimId, "NEW_VALUE"))
.ReturnsAsync((DimensionAttributeValue)null);

// Act
var result = await service.ResolveSegmentValuesAsync(new[] { input });

// Assert
Assert.AreEqual(1, result.Count);
mockRepo.Verify(r => r.AddValueAsync(It.IsAny<DimensionAttributeValue>()), Times.Once);
}

[Test]
public async Task Should_Reuse_Existing_Value_When_Exists()
{
// Arrange
var existingValue = new DimensionAttributeValue(customListDimId, "EXISTING_VALUE");
mockRepo.Setup(r => r.FindValueAsync(customListDimId, "EXISTING_VALUE"))
.ReturnsAsync(existingValue);

// Act
var result = await service.ResolveSegmentValuesAsync(
new[] { new DimensionSegmentInput(customListDimId, "EXISTING_VALUE") });

// Assert
Assert.AreEqual(existingValue.Id, result.First().DimensionAttributeValueId);
mockRepo.Verify(r => r.AddValueAsync(It.IsAny<DimensionAttributeValue>()), Times.Never);
}
}

Integration Testing

[TestFixture]
public class CrossModuleIntegrationTests
{
[Test]
public async Task Customer_Default_Dimensions_Should_Be_Inherited_By_Invoice()
{
// Arrange
var customer = await CreateCustomerWithDefaultDimensions();
var invoice = new CustomerInvoice(customer.Id);

// Act
await invoice.InheritCustomerDimensionsAsync(customer, valueSetService);

// Assert
Assert.IsTrue(invoice.Lines.All(l => l.DimensionSegments.Any()));
var inheritedSegments = invoice.Lines.First().DimensionSegments;
Assert.Contains(departmentSegment, inheritedSegments);
}
}

Future Enhancements

Advanced Features

  • Machine Learning Integration: Intelligent dimension value suggestions based on transaction patterns
  • Advanced Validation Rules: Complex cross-dimensional business rules engine
  • Hierarchical Dimensions: Support for dimension value hierarchies and rollups
  • Temporal Dimensions: Time-based dimension values for seasonal or project-based accounting

Performance Optimizations

  • Distributed Caching: Redis-based caching for multi-instance deployments
  • Read Replicas: Separate read/write paths for dimension lookups
  • Bulk Import Optimization: Optimized bulk dimension value import processes
  • Search Indexing: Elasticsearch integration for fast dimension value search

Integration Enhancements

  • External System Integration: Connectors for external master data systems
  • Real-time Synchronization: Event-driven synchronization with external systems
  • API Rate Limiting: Advanced rate limiting for dimension lookup APIs
  • Multi-Tenant Isolation: Enhanced support for multi-tenant dimension management

Implementation References

Cross-Module References


Last Updated: [Current Date] | Version: 1.0 | Status: Planning Phase

Priority Items:

  1. Complete DimensionAttribute aggregate implementation and documentation
  2. Implement DimensionAttributeValueSet aggregate and domain service
  3. Create comprehensive integration testing framework
  4. Implement dynamic dimension value provider registry
  5. Performance optimization for high-volume dimension lookups