Public/Get-LedgerAccountsPayable.ps1

<#
.SYNOPSIS
Reports the open accounts payable (leverantörsreskontra) with an aging breakdown.

.DESCRIPTION
Returns the outstanding supplier invoices — those that have been posted but are
not fully paid (status Booked or Partial) — with the number of days each is
overdue relative to a reference date and an aging bucket (Current, 1-30, 31-60,
61-90 or 90+ days past due).

By default one object is returned per open invoice. With -Summary, one object is
returned per aging bucket with the invoice count and the total outstanding amount,
which is useful for planning upcoming payments.

The sum of the outstanding amounts reconciles against the balance of the payable
account (2440) in the general ledger.

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

.PARAMETER AsOf
The reference date used to calculate how many days each invoice is overdue.
Defaults to today.

.PARAMETER SupplierNumber
Optional. If specified, only invoices for this supplier are included.

.PARAMETER Summary
Return one row per aging bucket (with Count and Total) instead of one row per
invoice.

.EXAMPLE
Get-LedgerAccountsPayable -JournalPath .\MinFirma.ledger

Lists every open supplier invoice with its remaining amount, days overdue and
aging bucket.

.EXAMPLE
Get-LedgerAccountsPayable -AsOf '2024-05-01' -Summary

Shows the outstanding total per aging bucket as of 1 May 2024.
#>

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

        [Parameter()]
        [datetime]$AsOf = (Get-Date).Date,

        [Parameter()]
        [string]$SupplierNumber,

        [Parameter()]
        [switch]$Summary
    )
    $JournalPath = Resolve-LedgerJournalPath -JournalPath $JournalPath

    $bucketOrder = @('Current', '1-30', '31-60', '61-90', '90+')

    $open = @(Get-LedgerSupplierInvoice -JournalPath $JournalPath -Unpaid)
    if ($PSBoundParameters.ContainsKey('SupplierNumber')) {
        $open = @($open | Where-Object { $_.SupplierNumber -eq $SupplierNumber })
    }

    $rows = foreach ($inv in $open) {
        $daysOverdue = [int]([datetime]$AsOf - [datetime]$inv.DueDate).TotalDays
        if ($daysOverdue -lt 0) { $daysOverdue = 0 }
        $bucket =
            if ($daysOverdue -le 0) { 'Current' }
            elseif ($daysOverdue -le 30) { '1-30' }
            elseif ($daysOverdue -le 60) { '31-60' }
            elseif ($daysOverdue -le 90) { '61-90' }
            else { '90+' }

        [PSCustomObject]@{
            InvoiceNumber   = $inv.InvoiceNumber
            SupplierNumber  = $inv.SupplierNumber
            SupplierName    = $inv.SupplierName
            InvoiceDate     = $inv.InvoiceDate
            DueDate         = $inv.DueDate
            Total           = $inv.Total
            RemainingAmount = $inv.RemainingAmount
            DaysOverdue     = $daysOverdue
            AgingBucket     = $bucket
            Status          = $inv.Status
        }
    }
    $rows = @($rows)

    if (-not $Summary) {
        return ($rows | Sort-Object DueDate, InvoiceNumber)
    }

    foreach ($bucket in $bucketOrder) {
        $inBucket = @($rows | Where-Object { $_.AgingBucket -eq $bucket })
        $total = ($inBucket | Measure-Object -Property RemainingAmount -Sum).Sum
        if (-not $total) { $total = [decimal]0 }
        [PSCustomObject]@{
            AgingBucket = $bucket
            Count       = $inBucket.Count
            Total       = [Math]::Round([decimal]$total, 2)
        }
    }
}