Public/Get-LedgerBalance.ps1

<#
.SYNOPSIS
Generates a trial balance (saldobalans) for a fiscal year.

.DESCRIPTION
Reads all verifications in the specified fiscal year and aggregates amounts
per account. The opening balance is read from the fiscal year's opening balance
metadata (ib.txt) and reported as OpeningBalance, while all verifications are
summed into Debit and Credit. The closing balance (Balance) is
OpeningBalance + Debit - Credit. Account names are resolved from the journal's
chart of accounts.

.PARAMETER JournalPath
The path to an existing journal directory.

.PARAMETER FiscalYear
The fiscal year identifier (e.g. '2024-01_2024-12').

.EXAMPLE
Get-LedgerBalance -JournalPath .\MinFirma.ledger -FiscalYear '2024-01_2024-12'

Returns the trial balance with OpeningBalance, Debit, Credit, and Balance per account.

.EXAMPLE
Get-LedgerBalance -JournalPath .\MinFirma.ledger -FiscalYear '2024-01_2024-12' |
    Format-Table AccountNumber, AccountName, OpeningBalance, Debit, Credit, Balance

Displays the trial balance as a formatted table showing the opening balance
(ingående saldo), period debit/credit totals, and the closing balance
(utgående saldo).
#>

function Get-LedgerBalance {
    [OutputType([pscustomobject])]
    [CmdletBinding()]
    param (
        [Parameter()]
        [string]$JournalPath,

        [Parameter(ValueFromPipelineByPropertyName)]
        [Alias('Name')]
        [string]$FiscalYear
    )
    process {
        $JournalPath = Resolve-LedgerJournalPath -JournalPath $JournalPath
        $FiscalYear = Resolve-LedgerFiscalYear -FiscalYear $FiscalYear -JournalPath $JournalPath

        $YearDir = Join-Path $JournalPath $FiscalYear
        if (-not (Test-Path $YearDir -PathType Container)) {
            throw "Fiscal year not found: $FiscalYear"
        }

        # Load chart of accounts for name lookup
        $AccountNames = @{}
        $KontoplanFile = Join-Path $JournalPath 'accounts.txt'
        if (Test-Path $KontoplanFile) {
            foreach ($Line in (Get-Content $KontoplanFile)) {
                if ($Line -match '^(\d+)\t(.+)$') {
                    $AccountNames[$Matches[1]] = $Matches[2]
                }
            }
        }

        # Read all verification files
        $Files = Get-ChildItem -Path $YearDir -Filter 'ver*.txt' -File -ErrorAction SilentlyContinue
        $OpeningRows = Read-LedgerOpeningBalance -YearDir $YearDir
        if (-not $Files -and -not $OpeningRows) {
            return
        }

        # Aggregate amounts per account
        $Totals = @{}

        # Seed opening balances from ib.txt (ingående balans metadata).
        foreach ($Row in $OpeningRows) {
            $AccNum = $Row.Account
            if (-not $Totals.ContainsKey($AccNum)) {
                $Totals[$AccNum] = @{ Opening = [decimal]0; Debit = [decimal]0; Credit = [decimal]0 }
            }
            $Totals[$AccNum].Opening += [decimal]$Row.Amount
        }

        foreach ($File in $Files) {
            $Lines = Get-Content $File.FullName

            foreach ($Line in $Lines) {
                if ($Line -match '^(\d+)\t([^\t]+)') {
                    $AccNum = $Matches[1]
                    $Amount = [decimal]$Matches[2]

                    if (-not $Totals.ContainsKey($AccNum)) {
                        $Totals[$AccNum] = @{ Opening = [decimal]0; Debit = [decimal]0; Credit = [decimal]0 }
                    }

                    if ($Amount -gt 0) {
                        $Totals[$AccNum].Debit += $Amount
                    }
                    elseif ($Amount -lt 0) {
                        $Totals[$AccNum].Credit += [Math]::Abs($Amount)
                    }
                }
            }
        }

        # Build sorted result
        $Totals.GetEnumerator() | Sort-Object Key | ForEach-Object {
            $AccNum = $_.Key
            $Opening = $_.Value.Opening
            $Debit = $_.Value.Debit
            $Credit = $_.Value.Credit

            [PSCustomObject]@{
                AccountNumber  = $AccNum
                AccountName    = if ($AccountNames.ContainsKey($AccNum)) { $AccountNames[$AccNum] } else { '' }
                OpeningBalance = $Opening
                Debit          = $Debit
                Credit         = $Credit
                Balance        = $Opening + $Debit - $Credit
            }
        }
    }
}