Public/Get-ProjectedTransactions.ps1

function Get-ProjectedTransactions {
    <#
    .SYNOPSIS
        Generates projected transactions based on billers, earnings, and transfers.
 
    .DESCRIPTION
        Creates a list of projected transactions for a specified date range based on
        configured billers, earnings, and transfers. Calculates running balance for
        each transaction. Transactions use the AccountId from their source entity.
        Transfers create two transactions: outflow from source and inflow to destination.
 
    .PARAMETER StartDate
        The start date for the projection period.
 
    .PARAMETER EndDate
        The end date for the projection period.
 
    .PARAMETER Account
        Optional account name to filter transactions. Only returns transactions
        for the specified account.
 
    .PARAMETER InitialBalance
        The starting balance for the projection. Defaults to 0.
 
    .PARAMETER Budget
        Optional budget name to target. Uses active budget if not specified.
 
    .PARAMETER DataPath
        Optional custom path for data storage. Overrides budget-based paths.
 
    .EXAMPLE
        Get-ProjectedTransactions -StartDate "2025-01-01" -EndDate "2025-03-31"
 
    .EXAMPLE
        Get-ProjectedTransactions -StartDate (Get-Date) -EndDate (Get-Date).AddMonths(6) -InitialBalance 1000
 
    .EXAMPLE
        Get-ProjectedTransactions -StartDate "2025-01-01" -EndDate "2025-12-31" -Account "Joint Billing"
 
    .EXAMPLE
        Get-ProjectedTransactions -StartDate "2025-01-01" -EndDate "2025-12-31" -Budget "japan-holiday-2026"
 
    .OUTPUTS
        Array of Transaction objects
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [datetime]$StartDate,

        [Parameter(Mandatory)]
        [datetime]$EndDate,

        [Parameter()]
        [string]$Account,

        [Parameter()]
        [decimal]$InitialBalance = 0,

        [Parameter()]
        [string]$Budget,

        [Parameter()]
        [string]$DataPath
    )

    $resolvedPath = Resolve-DataPath -DataPath $DataPath -Budget $Budget
    if (-not $resolvedPath) { return @() }

    # Get all entities
    $billers = Read-EntityData -EntityType 'Biller' -DataPath $resolvedPath
    $earnings = Read-EntityData -EntityType 'Earning' -DataPath $resolvedPath
    $transfers = Read-EntityData -EntityType 'Transfer' -DataPath $resolvedPath
    $accounts = Read-EntityData -EntityType 'Account' -DataPath $resolvedPath

    # Resolve account filter if specified
    $filterAccountId = $null
    if ($Account) {
        $matchingAccount = $accounts | Where-Object { $_.Name -eq $Account }
        if (-not $matchingAccount) {
            Write-Error "Account '$Account' not found."
            return @()
        }
        $filterAccountId = $matchingAccount.Id
    }

    # Create transaction list
    $transactions = [System.Collections.ArrayList]::new()

    # Helper function to add next occurrence
    function Get-NextDate {
        param([datetime]$CurrentDate, [string]$Frequency)
        switch ($Frequency) {
            'Daily' { return $CurrentDate.AddDays(1) }
            'Weekly' { return $CurrentDate.AddDays(7) }
            'BiWeekly' { return $CurrentDate.AddDays(14) }
            'Monthly' { return $CurrentDate.AddMonths(1) }
            'Bimonthly' { return $CurrentDate.AddMonths(2) }
            'Quarterly' { return $CurrentDate.AddMonths(3) }
            'Yearly' { return $CurrentDate.AddYears(1) }
        }
    }

    # Process earnings (positive transactions)
    foreach ($earning in $earnings) {
        # Skip if filtering by account and this earning doesn't match
        if ($filterAccountId -and $earning.AccountId -ne $filterAccountId) { continue }

        $currentDate = Get-NextOccurrence -StartDate $earning.StartDate -Frequency $earning.Frequency -FromDate $StartDate

        while ($currentDate -le $EndDate) {
            $transaction = [Transaction]@{
                Date      = $currentDate
                Name      = $earning.Name
                Amount    = $earning.Amount
                Balance   = 0  # Will calculate later
                Type      = 'Earning'
                AccountId = $earning.AccountId
            }
            $transactions.Add($transaction) | Out-Null
            $currentDate = Get-NextDate -CurrentDate $currentDate -Frequency $earning.Frequency
        }
    }

    # Process billers (negative transactions)
    foreach ($biller in $billers) {
        # Skip if filtering by account and this biller doesn't match
        if ($filterAccountId -and $biller.AccountId -ne $filterAccountId) { continue }

        $currentDate = Get-NextOccurrence -StartDate $biller.StartDate -Frequency $biller.Frequency -FromDate $StartDate

        while ($currentDate -le $EndDate) {
            $transaction = [Transaction]@{
                Date      = $currentDate
                Name      = $biller.Name
                Amount    = -$biller.Amount  # Negative for expenses
                Balance   = 0  # Will calculate later
                Type      = 'Biller'
                AccountId = $biller.AccountId
            }
            $transactions.Add($transaction) | Out-Null
            $currentDate = Get-NextDate -CurrentDate $currentDate -Frequency $biller.Frequency
        }
    }

    # Process transfers (creates two transactions: outflow and inflow)
    foreach ($transfer in $transfers) {
        $currentDate = Get-NextOccurrence -StartDate $transfer.StartDate -Frequency $transfer.Frequency -FromDate $StartDate

        while ($currentDate -le $EndDate) {
            # Outflow transaction (from source account)
            if (-not $filterAccountId -or $transfer.FromAccountId -eq $filterAccountId) {
                $outflowTransaction = [Transaction]@{
                    Date      = $currentDate
                    Name      = "$($transfer.Name) (Transfer Out)"
                    Amount    = -$transfer.Amount  # Negative for outflow
                    Balance   = 0
                    Type      = 'Transfer'
                    AccountId = $transfer.FromAccountId
                }
                $transactions.Add($outflowTransaction) | Out-Null
            }

            # Inflow transaction (to destination account)
            if (-not $filterAccountId -or $transfer.ToAccountId -eq $filterAccountId) {
                $inflowTransaction = [Transaction]@{
                    Date      = $currentDate
                    Name      = "$($transfer.Name) (Transfer In)"
                    Amount    = $transfer.Amount  # Positive for inflow
                    Balance   = 0
                    Type      = 'Transfer'
                    AccountId = $transfer.ToAccountId
                }
                $transactions.Add($inflowTransaction) | Out-Null
            }

            $currentDate = Get-NextDate -CurrentDate $currentDate -Frequency $transfer.Frequency
        }
    }

    # Sort transactions by date
    $sortedTransactions = $transactions | Sort-Object -Property Date

    # Add initial balance as the first entry
    $initialTransaction = [Transaction]@{
        Date      = $StartDate
        Name      = 'Initial Balance'
        Amount    = 0
        Balance   = $InitialBalance
        Type      = 'Balance'
        AccountId = $filterAccountId
    }

    # Create final list with initial balance first
    $finalTransactions = [System.Collections.ArrayList]::new()
    $finalTransactions.Add($initialTransaction) | Out-Null

    # Calculate running balance for remaining transactions
    $runningBalance = $InitialBalance
    foreach ($transaction in $sortedTransactions) {
        $runningBalance += $transaction.Amount
        $transaction.Balance = $runningBalance
        $finalTransactions.Add($transaction) | Out-Null
    }

    return $finalTransactions
}