TaxPostingGroup
File: /docs/domain/general-ledger/aggregates/tax-posting-group.aggregate.md
Purpose
The TaxPostingGroup aggregate defines the general ledger account configuration for tax-related transactions, establishing the bridge between tax calculations and GL posting. It determines which accounts receive tax amounts based on tax direction (payable vs receivable) and ensures accurate financial reporting of tax obligations and recoverable amounts.
Business Context
Tax transactions must be posted to appropriate GL accounts to maintain accurate financial records and enable proper tax reporting. The TaxPostingGroup aggregate encapsulates the account assignment logic that ensures:
- Tax Payable Amounts: Posted to liability accounts when taxes are owed to authorities
- Tax Receivable Amounts: Posted to asset accounts when taxes can be recovered from authorities
- Account Segregation: Different tax types can use different account structures
- Regulatory Compliance: Account structures support tax authority reporting requirements
Aggregate Structure
Root Entity: TaxPostingGroup
- Identity:
Guid Id(inherited from AggregateRootBase) - Core Properties: Code, Description, TaxPayableLedgerAccountId, TaxReceivableLedgerAccountId
- Navigation Properties: TaxPayableLedgerAccount, TaxReceivableLedgerAccount
- Child Entities: None (simple aggregate)
Business Rules & Invariants
1. Account Configuration Requirements
- Rule: At least one account (payable or receivable) must be configured
- Enforcement: Constructor and Update method validation
- Rationale: Ensures the posting group can handle tax transactions
2. Code Uniqueness
- Rule: Each posting group must have a unique code within the system
- Enforcement: Application layer validation during creation and updates
- Rationale: Enables clear identification and prevents configuration confusion
3. Account Type Appropriateness
- Rule: Payable accounts should be liability accounts, receivable accounts should be asset accounts
- Enforcement: Application layer validation using account type metadata
- Rationale: Ensures proper balance sheet classification of tax amounts
4. Description Requirement
- Rule: Description must be provided to explain the posting group's purpose
- Enforcement: Constructor and Update method validation
- Rationale: Supports user understanding and audit trail clarity
Usage Patterns
Common Posting Group Configurations
VAT/GST Systems
// Standard VAT with both directions
var vatPosting = new TaxPostingGroup(
"VAT-STANDARD",
"Standard VAT posting with 20% rate",
taxPayableAccountId: vatPayableAccount.Id, // Liability: VAT Payable
taxReceivableAccountId: vatReceivableAccount.Id // Asset: VAT Receivable
);
Sales Tax Systems
// Sales tax (output only)
var salesTaxPosting = new TaxPostingGroup(
"SALES-TAX",
"Sales tax for domestic transactions",
taxPayableAccountId: salesTaxPayableAccount.Id,
taxReceivableAccountId: null // No input tax recovery
);
Import Duty Systems
// Import duties (input only)
var dutyPosting = new TaxPostingGroup(
"IMPORT-DUTY",
"Import duties and customs charges",
taxPayableAccountId: null, // No output duty
taxReceivableAccountId: dutyReceivableAccount.Id
);
Account Assignment Logic
The posting group determines account usage based on tax direction:
// Output Tax (Sales) - Use Payable Account
if (taxDirection == TaxDirection.Output && postingGroup.CanHandlePayableTax())
{
creditAccount = postingGroup.TaxPayableLedgerAccountId;
}
// Input Tax (Purchases) - Use Receivable Account
if (taxDirection == TaxDirection.Input && postingGroup.CanHandleReceivableTax())
{
debitAccount = postingGroup.TaxReceivableLedgerAccountId;
}
Integration Points
With TaxCode Aggregate
- Relationship: One-to-Many (One posting group can serve multiple tax codes)
- Usage: Tax codes reference posting groups for account determination
- Constraint: Tax codes cannot exist without valid posting group assignment
With Account Aggregate
- Relationship: Many-to-One (Multiple posting groups can use the same accounts)
- Usage: Posting groups reference GL accounts for transaction posting
- Constraint: Account assignments should reference valid, active GL accounts
With GL Posting Engine
- Purpose: Provides account assignment for automated tax posting
- Usage: Posting engine queries posting group for account IDs during transaction creation
- Integration: Account IDs used directly in journal entry creation
With Tax Calculation Engine
- Purpose: Works with tax codes to provide complete posting specification
- Usage: Tax calculations include posting group context for GL entry generation
- Flow: Calculate tax amount → Determine posting group → Assign accounts → Create GL entries
State Management
Posting Group States
The aggregate maintains configuration state without explicit lifecycle states:
- Configured: Has at least one account assignment and can handle transactions
- Incomplete: Missing required account assignments for intended tax scenarios
Configuration Validation
// Check capability for specific tax scenarios
bool canHandleOutputTax = postingGroup.CanHandlePayableTax();
bool canHandleInputTax = postingGroup.CanHandleReceivableTax();
// Ensure posting group matches tax code requirements
if (taxCodeDirection == TaxDirection.Output && !canHandleOutputTax)
{
throw new InvalidTaxPostingConfigurationException();
}
Performance Considerations
Caching Strategy
- Account References: Cache posting group configurations for frequent tax calculations
- Validation Results: Cache account type validation results to avoid repeated queries
- Code Lookups: Index on Code field for efficient posting group resolution
Query Optimization
- Eager Loading: Include account navigation properties when posting groups are loaded for transaction processing
- Projection: Load only necessary fields when posting groups are used for account ID resolution only
Design Decisions
Account Optionality
- Decision: Allow null values for either payable or receivable accounts
- Rationale: Supports tax systems that only have one direction (e.g., sales tax, import duties)
- Alternative: Could have required both accounts but would complicate single-direction tax scenarios
Simple Aggregate Design
- Decision: Keep posting group as a simple aggregate without child entities
- Rationale: Account assignment is straightforward and doesn't require complex sub-entities
- Alternative: Could have modeled account assignments as separate entities but adds unnecessary complexity
Code-Based Identification
- Decision: Use meaningful codes for user identification rather than relying solely on GUIDs
- Rationale: Users need recognizable identifiers for configuration and troubleshooting
- Pattern: Follows established ERP patterns for master data identification
Account Type Enforcement
- Decision: Enforce account type appropriateness at application layer rather than domain
- Rationale: Domain layer doesn't have access to account metadata; validation requires account repository
- Implementation: Application services validate account types during creation and updates
Common Tax Scenarios
Multi-Jurisdiction Setup
// Different posting groups for different jurisdictions
var domesticVAT = new TaxPostingGroup("VAT-DOMESTIC", "Domestic VAT", payableId, receivableId);
var euVAT = new TaxPostingGroup("VAT-EU", "EU VAT", euPayableId, euReceivableId);
var exportVAT = new TaxPostingGroup("VAT-EXPORT", "Export VAT", null, exportReceivableId);
Complex Tax Structures
// Separate posting groups for different tax components
var federalTax = new TaxPostingGroup("FED-TAX", "Federal Tax", fedPayableId, fedReceivableId);
var stateTax = new TaxPostingGroup("STATE-TAX", "State Tax", statePayableId, stateReceivableId);
var localTax = new TaxPostingGroup("LOCAL-TAX", "Local Tax", localPayableId, null);