Public/Get-CwmAgreement.ps1

function Get-CwmAgreement {
    [CmdletBinding()]
    [OutputType([Agreement[]])]
    Param (
        [Parameter(Mandatory = $False, Position = 0, ValueFromPipelineByPropertyName = $True, ParameterSetName = "NoId")]
        [int]$CompanyId,

        [Parameter(Mandatory = $False, Position = 0, ParameterSetName = "Id")]
        [int]$AgreementId,

        [Parameter(Mandatory = $False, ParameterSetName = "NoId")]
        [int]$OpportunityId,

        [Parameter(Mandatory = $false)]
        [string]$AuthString = $global:CwAuthString,

        [Parameter(Mandatory = $False, ParameterSetName = "NoId")]
        [switch]$ShowAll,

        [Parameter(Mandatory = $False, ParameterSetName = "NoId")]
        [string]$Status = 'Active',

        [Parameter(Mandatory = $False, ParameterSetName = "NoId")]
        [string]$PageSize = 1000
    )

    $VerbosePrefix = "Get-CwmAgreement:"

    $Uri = "https://api-na.myconnectwise.net/"
    $Uri += 'v4_6_Release/apis/3.0/'
    $Uri += "finance/agreements"

    $QueryParams = @{}
    $QueryParams.page = 1
    $QueryParams.pageSize = $PageSize

    $Conditions = @{}
    if (-not $AgreementId) {
        $Conditions.agreementStatus = $Status
    }

    if ($CompanyId) {
        $Conditions.'company/id' = $CompanyId
    }

    if ($AgreementId) {
        $Conditions.'id' = $AgreementId
    }

    if (!($ShowAll)) {
        #$Conditions.'noEndingDateFlag' = $true # can't remember why I did this
    } else {
        $Conditions.Remove('agreementStatus')
    }

    if ($OpportunityId) {
        $Conditions.'opportunity/id' = $OpportunityId
    }

    $ApiParams = @{}
    $ApiParams.Uri = $Uri
    $ApiParams.AuthString = $AuthString
    $ApiParams.QueryParams = $QueryParams
    if ($Conditions.Count -gt 0) {
        $ApiParams.Conditions = $Conditions
    }

    $ApiParams = @{}
    $ApiParams.UriPath = 'finance/agreements'
    $ApiParams.Conditions = $Conditions
    $ApiParams.QueryParameters = @{}
    $ApiParams.QueryParameters.page = 1
    $ApiParams.QueryParameters.pageSize = $PageSize

    #$ReturnValue = Invoke-CwmApiCall @ApiParams
    $ReturnValue = Invoke-CwmApiQuery @ApiParams
    if ($ShowAll) {
        if ($ReturnValue.Count -eq $PageSize) {
            $QueryParams.page++
            $ApiParams.QueryParams = $QueryParams
            $MoreValues = Invoke-CwmApiCall @ApiParams
            $ReturnValue += $MoreValues
        }
    }

    $ReturnObject = @()
    foreach ($r in $ReturnValue) {
        $ThisObject = New-Object Agreement
        $ThisObject.AgreementId = $r.id
        $ThisObject.FullData = $r

        $ThisObject.Name = $r.name
        $ThisObject.AgreementType = $r.type.name
        $ThisObject.AgreementTypeId = $r.type.id
        $ThisObject.CompanyName = $r.company.name
        $ThisObject.Status = $r.agreementStatus

        $ThisObject.StartDate = $r.startDate
        if ($r.endDate) {
            $ThisObject.EndDate = $r.endDate
        }

        $ThisObject.ApplicationUnit = $r.applicationUnits
        $ThisObject.ApplicationLimit = $r.applicationLimit
        $ThisObject.ApplicationCycle = $r.applicationCycle

        $ThisObject.IsOneTime = $r.oneTimeFlag
        $ThisObject.CarryOverUnused = $r.carryOverUnused
        $ThisObject.CarryOverExpireDays = $r.expiredDays
        $ThisObject.ExpireWhenZero = $r.expireWhenZero

        # Calculate available hours
        # this is frankly, a ridiculous process and CW needs to expose an endpoint for it
        # in my org, we apply new agreement allotments at the beginning of the month
        # this triggers the expiration timer of the old hours that have carryover
        # no idea if it triggers it counting from that day or the day before, hopefully
        # it doesn't matter

        $TimeEntries = Get-CwmTimeEntry -AgreementId $ThisObject.AgreementId
        switch ($ThisObject.ApplicationCycle) {
            'CalendarMonth' {
                $AvailableHours = @()

                $CurrentDate = $ThisObject.StartDate

                do {
                    # set dates
                    if ($NextApplicationDate) {
                        $StartDate = $NextApplicationDate
                    } else {
                        $StartDate = $CurrentDate
                    }
                    $NextApplicationDate = $StartDate.AddMonths(1).AddDays((-1 * $StartDate.Day) + 1)
                    Write-Verbose "$VerbosePrefix NextApplicationDate: $NextApplicationDate"

                    # accrue monthly hours
                    $HourAddition = "" | Select-Object `
                    @{Name = 'AddDate'; Expression = {$StartDate}},
                    @{Name = 'ExpirationDate'; Expression = {$NextApplicationDate.AddDays($ThisObject.CarryOverExpireDays)}},
                    @{Name = 'Hours'; Expression = {$ThisObject.ApplicationLimit}},
                    StartingBalance, UsedHours
                    $AvailableHours += $HourAddition

                    # time entries for this month
                    $ValidTimeEntries = $TimeEntries | Where-Object { $_.FullData.timeEnd -gt $StartDate -and $_.FullData.timeEnd -lt $NextApplicationDate }
                    $HoursThisMonth = ($ValidTimeEntries.FullData.actualHours | measure-Object -Sum).Sum * -1

                    # cycle through valid hour entries
                    $ValidAvailableHours = $AvailableHours | Where-Object { $_.ExpirationDate -gt $StartDate }
                    $HourAddition.StartingBalance = ($ValidAvailableHours.Hours | Measure-Object -Sum).Sum
                    $HourAddition.UsedHours = $HoursThisMonth
                    $BalanceForward = 0
                    foreach ($HourEntry in ($ValidAvailableHours | Sort-Object ExpirationDate)) {
                        $HourEntry.Hours += $HoursThisMonth
                        if ($HourEntry.Hours -lt 0) {
                            $BalanceForward = $HourEntry.Hours
                            $HourEntry.Hours = 0
                        }
                    }

                    $ThisObject.StartingBalance = (($AvailableHours | Where-Object { $_.ExpirationDate -ge $CurrentDate }).Hours | Measure-Object -Sum).Sum

                    Write-Verbose "$VerbosePrefix $CurrentDate`: $($ThisObject.StartingBalance)"

                    $CurrentDate = $EndDate

                } while ($NextApplicationDate -lt (get-date))
            }
            '' {
                $ThisObject.StartingBalance = $ThisObject.ApplicationLimit
                Write-Verbose "$VerbosePrefix ApplicationCycle not set"
                break
            }
            default {
                Write-Warning "$VerbosePrefix unhandled ApplicationLimit: $($ThisObject.ApplicationCycle)"
            }
        }

        if ($thisObject.AgreementId -eq 286) {
            $global:tAvailableHours = $AvailableHours
        }
        $ReturnObject += $ThisObject
    }

    $ReturnObject
}