Public/Copy-LedgerOpeningBalance.ps1

<#
.SYNOPSIS
Copies closing balances as opening balances into a new fiscal year.

.DESCRIPTION
Reads the trial balance from a source fiscal year, takes all balance-sheet
accounts (1xxx assets and 2xxx equity/liabilities), and writes them as the
opening balance metadata (ib.txt) of the target fiscal year. The opening balance
is stored as metadata, not as a verification, so verification numbering starts at
ver0001 for the first real entry.

.PARAMETER JournalPath
The path to an existing journal directory.

.PARAMETER FromFiscalYear
The source fiscal year to read closing balances from.

.PARAMETER ToFiscalYear
The target fiscal year where the opening balance entry will be created.

.EXAMPLE
Copy-LedgerOpeningBalance -JournalPath .\MinFirma.ledger -FromFiscalYear '2024-01_2024-12' -ToFiscalYear '2025-01_2025-12'

Creates an opening balance verification in the 2025 fiscal year based on
2024's closing balances.

.EXAMPLE
Close-LedgerFiscalYear -JournalPath .\MinFirma.ledger -FiscalYear '2024-01_2024-12'
New-LedgerFiscalYear -JournalPath .\MinFirma.ledger -StartDate '2025-01-01' -EndDate '2025-12-31'
Copy-LedgerOpeningBalance -JournalPath .\MinFirma.ledger -FromFiscalYear '2024-01_2024-12' -ToFiscalYear '2025-01_2025-12'

Typical year-end workflow: close old year, create new, copy balances.
#>

function Copy-LedgerOpeningBalance {
    [CmdletBinding(SupportsShouldProcess)]
    param (
        [Parameter()]
        [string]$JournalPath,

        [Parameter(ValueFromPipelineByPropertyName)]
        [Alias('Name')]
        [string]$FromFiscalYear,

        [Parameter(Mandatory)]
        [string]$ToFiscalYear
    )
    process {
        $JournalPath = Resolve-LedgerJournalPath -JournalPath $JournalPath -SchemaCheck Write
        $FromFiscalYear = Resolve-LedgerFiscalYear -FiscalYear $FromFiscalYear -JournalPath $JournalPath

        # Validate source fiscal year
        $FromDir = Join-Path $JournalPath $FromFiscalYear
        if (-not (Test-Path $FromDir -PathType Container)) {
            throw "Source fiscal year not found: $FromFiscalYear"
        }

        # Validate target fiscal year
        $ToDir = Join-Path $JournalPath $ToFiscalYear
        if (-not (Test-Path $ToDir -PathType Container)) {
            throw "Target fiscal year not found: $ToFiscalYear"
        }

        # Check target has no opening balance yet
        $ExistingIb = Get-LedgerOpeningBalancePath -YearDir $ToDir
        if (Test-Path $ExistingIb -PathType Leaf) {
            throw "Target fiscal year $ToFiscalYear already has an opening balance (ib.txt)."
        }

        # Get balance accounts from source year
        $Balance = Get-LedgerBalance -JournalPath $JournalPath -FiscalYear $FromFiscalYear
        if (-not $Balance) {
            throw "No entries found in source fiscal year $FromFiscalYear."
        }

        # Filter to balance sheet accounts (1xxx and 2xxx)
        $BalanceAccounts = $Balance | Where-Object { $_.AccountNumber -match '^[12]' -and $_.Balance -ne 0 }

        # Calculate year's result from P&L accounts (3xxx-8xxx) and carry it into
        # the equity result account. The account depends on the journal's
        # CompanyType (AB -> 2099, EF -> 2019), falling back to 2099.
        $PLAccounts = $Balance | Where-Object { $_.AccountNumber -match '^[3-8]' }
        $YearResult = if ($PLAccounts) { ($PLAccounts | Measure-Object -Property Balance -Sum).Sum } else { [decimal]0 }

        if (-not $BalanceAccounts -and $YearResult -eq 0) {
            throw "No balance sheet accounts with non-zero balances in $FromFiscalYear."
        }

        # Merge year's result into the equity result account
        if ($YearResult -ne 0) {
            $ResultAccount = Resolve-LedgerResultCarryAccount -JournalPath $JournalPath
            $ExistingResult = $BalanceAccounts | Where-Object { $_.AccountNumber -eq $ResultAccount }
            if ($ExistingResult) {
                $BalanceAccounts = $BalanceAccounts | Where-Object { $_.AccountNumber -ne $ResultAccount }
                $YearResult += $ExistingResult.Balance
            }
            $BalanceAccounts = @($BalanceAccounts) + @([PSCustomObject]@{
                AccountNumber = $ResultAccount
                AccountName   = 'Årets resultat'
                Debit         = [decimal]0
                Credit        = [decimal]0
                Balance       = $YearResult
            })
        }

        if (-not $BalanceAccounts) {
            throw "No balance sheet accounts with non-zero balances in $FromFiscalYear."
        }

        # Write the opening balance as metadata (ib.txt), not as a verification.
        $IbRows = foreach ($Acc in $BalanceAccounts) {
            [PSCustomObject]@{ Account = $Acc.AccountNumber; Amount = $Acc.Balance }
        }
        if ($PSCmdlet.ShouldProcess($ToFiscalYear, "Copy opening balance from $FromFiscalYear")) {
            Write-LedgerOpeningBalance -YearDir $ToDir -Rows $IbRows
        }
    }
}