Public/Add-LedgerImpairment.ps1

<#
.SYNOPSIS
Books a write-down (nedskrivning) of an asset, or the reversal (återföring) of an
earlier write-down, as a verification.

.DESCRIPTION
Records a nedskrivning by creating a verification that debits a write-down
expense account and credits a value adjustment (accumulated write-down) account:

    <ExpenseAccount> +<amount>
    <AdjustmentAccount> -<amount>

With -Reverse the entry is turned around and records an återföring: the value
adjustment account is debited and ExpenseAccount (then a reversal account such as
8281 or 7760) is credited. A reversal can never exceed the write-downs accumulated
on the value adjustment account.

The amount can be given directly with -Amount, or derived from a holding recorded
with Set-LedgerHolding by giving -Account and -Name:

- Write-down: the holding is written down to its market value. The amount is
  BookValue - MarketValue for the holding (when it has a BookValue), otherwise the
  account's book value (including value adjustment accounts in the same ten-group)
  less its market value when it is the only holding on the account. After booking,
  the holding's BookValue is set to its market value.
- Reversal (-Reverse): the holding's book value is written back up to the lower of
  its market value and its acquisition cost (Cost). The holding needs both a
  BookValue and a Cost. After booking, BookValue is increased by the amount.

Typical BAS accounts: 8271/1359 (write-down) and 8281/1359 (reversal) for
long-term securities, and 7710/1098 and 7760/1098 for crypto assets reported as
intangible fixed assets (K3).

.PARAMETER JournalPath
The path to an existing journal directory. If omitted, uses the current journal
set via Set-LedgerCurrentJournal.

.PARAMETER FiscalYear
The fiscal year identifier (e.g. '2026-09_2027-08'). If omitted, uses the current
fiscal year set via Set-LedgerCurrentFiscalYear.

.PARAMETER Date
The date of the entry. Defaults to the fiscal year's end date (the balance date).

.PARAMETER Description
Description for the verification. Defaults to 'Nedskrivning <holding> till
marknadsvärde' / 'Återföring av nedskrivning <holding>' for a holding, otherwise
'Nedskrivning' / 'Återföring av nedskrivning'.

.PARAMETER ExpenseAccount
The write-down expense account to debit (e.g. 8271 or 7710), or with -Reverse the
reversal account to credit (e.g. 8281 or 7760).

.PARAMETER AdjustmentAccount
The value adjustment / accumulated write-down account (e.g. 1359 or 1098).

.PARAMETER Amount
The amount to write down or reverse. Always positive.

.PARAMETER Account
The account of the holding to write down or reverse.

.PARAMETER Name
The name of the holding to write down or reverse.

.PARAMETER Reverse
Book a reversal (återföring) of an earlier write-down instead of a write-down.

.EXAMPLE
Add-LedgerImpairment -JournalPath .\HEG.ledger -FiscalYear '2025-09_2026-08' `
    -ExpenseAccount 8271 -AdjustmentAccount 1359 -Amount 24348 `
    -Description 'Nedskrivning Knowit 200 st till 94,50 kr (bestående värdenedgång)'

Books a known write-down of 24 348 kr on the balance date.

.EXAMPLE
Add-LedgerImpairment -JournalPath .\HEG.ledger -FiscalYear '2026-09_2027-08' `
    -Account 1090 -Name 'Bitcoin (BTC)' -ExpenseAccount 7710 -AdjustmentAccount 1098

Writes the bitcoin holding down to its recorded market value (K3) and updates the
holding's book value.

.EXAMPLE
Add-LedgerImpairment -JournalPath .\HEG.ledger -FiscalYear '2027-09_2028-08' `
    -Account 1350 -Name 'Knowit' -ExpenseAccount 8281 -AdjustmentAccount 1359 -Reverse

Reverses the Knowit write-down as far as the recovered market value allows, but
never above the acquisition cost.
#>

function Add-LedgerImpairment {
    [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'DirectAmount')]
    param (
        [Parameter()]
        [string]$JournalPath,

        [Parameter(ValueFromPipelineByPropertyName)]
        [string]$FiscalYear,

        [Parameter()]
        [datetime]$Date,

        [Parameter()]
        [string]$Description,

        [Parameter(Mandatory)]
        [ValidatePattern('^\d+$')]
        [string]$ExpenseAccount,

        [Parameter(Mandatory)]
        [ValidatePattern('^\d+$')]
        [string]$AdjustmentAccount,

        [Parameter(Mandatory, ParameterSetName = 'DirectAmount')]
        [decimal]$Amount,

        [Parameter(Mandatory, ParameterSetName = 'Holding', ValueFromPipelineByPropertyName)]
        [ValidatePattern('^\d+$')]
        [string]$Account,

        [Parameter(Mandatory, ParameterSetName = 'Holding', ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string]$Name,

        [Parameter()]
        [switch]$Reverse
    )
    process {
        $ResolvedJournal = Resolve-LedgerJournalPath -JournalPath $JournalPath -SchemaCheck Write
        $ResolvedYear = Resolve-LedgerFiscalYear -FiscalYear $FiscalYear -JournalPath $ResolvedJournal

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

        $EntryDate = $Date
        if (-not $PSBoundParameters.ContainsKey('Date')) {
            $Year = Get-LedgerFiscalYear -JournalPath $ResolvedJournal | Where-Object { $_.Name -eq $ResolvedYear } | Select-Object -First 1
            $EntryDate = [datetime]::ParseExact($Year.EndDate.Trim(), 'yyyy-MM-dd', [System.Globalization.CultureInfo]::InvariantCulture)
        }

        $Holding = $null
        if ($PSCmdlet.ParameterSetName -eq 'Holding') {
            $Valuation = Get-LedgerHoldingValuation -JournalPath $ResolvedJournal -FiscalYear $ResolvedYear
            $Holding = $Valuation.Holdings | Where-Object { $_.Account -eq $Account -and $_.Name -eq $Name } | Select-Object -First 1
            if (-not $Holding) {
                throw "Holding '$Name' on account $Account not found in $ResolvedYear."
            }

            if ($Reverse) {
                if ($null -eq $Holding.BookValue -or $null -eq $Holding.Cost) {
                    throw "Holding '$Name' needs both BookValue and Cost to compute a reversal. Record them with Set-LedgerHolding or use -Amount."
                }
                $Booked = [Math]::Round([decimal]$Holding.Reversible, 2)
                if ($Booked -le 0) {
                    throw "Holding '$Name' has nothing to reverse (market value $($Holding.MarketValue), book value $($Holding.BookValue), cost $($Holding.Cost))."
                }
                if (-not $Description) { $Description = "Återföring av nedskrivning $Name" }
            }
            else {
                if ($null -ne $Holding.BookValue) {
                    $Booked = $Holding.BookValue - $Holding.MarketValue
                }
                else {
                    $OnAccount = @($Valuation.Holdings | Where-Object { $_.Account -eq $Account })
                    if ($OnAccount.Count -ne 1) {
                        throw "Holding '$Name' has no BookValue and shares account $Account with other holdings. Record its BookValue with Set-LedgerHolding or use -Amount."
                    }
                    $AccountRow = $Valuation.Accounts | Where-Object { $_.Account -eq $Account }
                    $Booked = $AccountRow.BookValue - $AccountRow.MarketValue
                }
                $Booked = [Math]::Round([decimal]$Booked, 2)
                if ($Booked -le 0) {
                    throw "Holding '$Name' is not below its book value (market value $($Holding.MarketValue)). Nothing to write down."
                }
                if ($null -eq $Holding.Cost) {
                    Write-Warning "Holding '$Name' has no Cost (anskaffningsvärde). Record it with Set-LedgerHolding -Cost so a later reversal can be computed."
                }
                if (-not $Description) { $Description = "Nedskrivning $Name till marknadsvärde" }
            }
        }
        else {
            $Booked = [Math]::Round([decimal]$Amount, 2)
            if ($Booked -le 0) {
                throw "Amount must be positive. Got: $Booked"
            }
        }
        if (-not $Description) {
            $Description = if ($Reverse) { 'Återföring av nedskrivning' } else { 'Nedskrivning' }
        }

        if ($Reverse) {
            # The value adjustment account carries a credit balance; a reversal must not exceed it.
            $AdjustmentRow = Get-LedgerBalance -JournalPath $ResolvedJournal -FiscalYear $ResolvedYear |
                Where-Object { $_.AccountNumber -eq $AdjustmentAccount } | Select-Object -First 1
            $Accumulated = if ($AdjustmentRow) { -[decimal]$AdjustmentRow.Balance } else { [decimal]0 }
            if ($Booked -gt $Accumulated) {
                throw "Reversal $Booked exceeds the write-downs accumulated on $AdjustmentAccount ($Accumulated)."
            }
        }

        $Action = if ($Reverse) { "Book reversal of write-down of $Booked" } else { "Book write-down of $Booked" }
        if (-not $PSCmdlet.ShouldProcess($ResolvedYear, $Action)) {
            return
        }

        $Sign = if ($Reverse) { -1 } else { 1 }
        $Entry = Add-LedgerEntry -JournalPath $ResolvedJournal -FiscalYear $ResolvedYear `
            -Date $EntryDate -Description $Description -PassThru -Rows @(
            @{ Account = $ExpenseAccount; Amount = $Sign * $Booked }
            @{ Account = $AdjustmentAccount; Amount = -$Sign * $Booked }
        )

        if ($Holding) {
            $NewBookValue = if ($Reverse) { $Holding.BookValue + $Booked } else { $Holding.MarketValue }
            Set-LedgerHolding -JournalPath $ResolvedJournal -FiscalYear $ResolvedYear -Account $Account -Name $Name `
                -BookValue $NewBookValue -WarningAction SilentlyContinue -Confirm:$false
        }

        [PSCustomObject]@{
            FiscalYear         = $ResolvedYear
            VerificationNumber = $Entry.VerificationNumber
            Date               = $EntryDate
            Description        = $Description
            Type               = if ($Reverse) { 'Reversal' } else { 'WriteDown' }
            ExpenseAccount     = $ExpenseAccount
            AdjustmentAccount  = $AdjustmentAccount
            Amount             = $Booked
            Holding            = if ($Holding) { $Name } else { $null }
        }
    }
}