انتقل إلى المحتوى الرئيسي

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);