Public/Set-LedgerTimeEntry.ps1

<#
.SYNOPSIS
Changes reported time.

.DESCRIPTION
Changes the hours, date, project, resource, text, billable flag or rate of an
open time entry. Invoiced entries are locked; credit the invoice with
Add-LedgerCreditInvoice first to change them.

When the project or customer changes and -Rate is not given, the rate is
resolved again from the new project/customer.

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

.PARAMETER EntryId
The id of the entry to change. Accepts pipeline input from Get-LedgerTimeEntry.

.PARAMETER Hours
The new number of hours (decimal or 'h:mm').

.PARAMETER Date
The new date.

.PARAMETER ProjectNumber
The new project.

.PARAMETER CustomerNumber
The new customer, for time not reported on a project.

.PARAMETER ResourceId
The new resource.

.PARAMETER Text
The new description.

.PARAMETER Billable
$true or $false.

.PARAMETER Rate
The new hourly rate.

.PARAMETER PassThru
If specified, returns the created/updated time entry. By default the command
produces no output.

.EXAMPLE
Set-LedgerTimeEntry -EntryId 42 -Hours 6 -Text 'Workshop, förkortad'

Corrects the hours and text of entry 42.

.EXAMPLE
Get-LedgerTimeEntry -ProjectNumber 1001 -Status Open | Set-LedgerTimeEntry -Rate 1200

Reprices all open time on project 1001 after a rate change.
#>

function Set-LedgerTimeEntry {
    [CmdletBinding(SupportsShouldProcess)]
    param (
        [Parameter()]
        [string]$JournalPath,

        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [int]$EntryId,

        [Parameter()]
        [string]$Hours,

        [Parameter()]
        [datetime]$Date,

        [Parameter()]
        [string]$ProjectNumber,

        [Parameter()]
        [string]$CustomerNumber,

        [Parameter()]
        [string]$ResourceId,

        [Parameter()]
        [string]$Text,

        [Parameter()]
        [bool]$Billable,

        [Parameter()]
        [ValidateRange(0, 1000000)]
        [decimal]$Rate,

        [Parameter()]
        [switch]$PassThru
    )
    process {
        $JournalPath = Resolve-LedgerJournalPath -JournalPath $JournalPath -SchemaCheck Write

        $journalLock = $null
        try {
            $journalLock = Enter-LedgerJournalLock -JournalPath $JournalPath
            Assert-LedgerJournalSchema -Path $JournalPath -Write

            $entries = @(Read-LedgerTimeEntries -JournalPath $JournalPath)
            $entry = $entries | Where-Object EntryId -eq $EntryId | Select-Object -First 1
            if (-not $entry) { throw "Time entry $EntryId does not exist." }
            $invoiceStatus = Get-LedgerTimeInvoiceStatusMap -JournalPath $JournalPath
            if (Test-LedgerTimeEntryInvoiced -Entry $entry -InvoiceStatus $invoiceStatus) {
                throw "Time entry $EntryId is on invoice $($entry.InvoiceNumber). Credit the invoice first to change it."
            }
            $oldMonth = $entry.Date.ToString('yyyy-MM')

            if ($PSBoundParameters.ContainsKey('Hours')) {
                $h = ConvertFrom-LedgerHours -Text $Hours
                Test-LedgerHours -Hours $h
                $entry.Hours = $h
            }
            if ($PSBoundParameters.ContainsKey('Date')) { $entry.Date = $Date.Date }
            if ($PSBoundParameters.ContainsKey('Text')) { $entry.Text = ConvertTo-LedgerTimeField $Text }

            $retarget = $PSBoundParameters.ContainsKey('ProjectNumber') -or $PSBoundParameters.ContainsKey('CustomerNumber') -or
                $PSBoundParameters.ContainsKey('ResourceId') -or $PSBoundParameters.ContainsKey('Billable') -or $PSBoundParameters.ContainsKey('Rate')
            if ($retarget) {
                $newProject = if ($PSBoundParameters.ContainsKey('ProjectNumber')) { $ProjectNumber } else { $entry.ProjectNumber }
                $newCustomer = if ($PSBoundParameters.ContainsKey('CustomerNumber')) { $CustomerNumber }
                               elseif ($PSBoundParameters.ContainsKey('ProjectNumber')) { '' }
                               else { $entry.CustomerNumber }
                $keepRate = -not ($PSBoundParameters.ContainsKey('ProjectNumber') -or $PSBoundParameters.ContainsKey('CustomerNumber'))
                $newRate = if ($PSBoundParameters.ContainsKey('Rate')) { $Rate } elseif ($keepRate) { $entry.Rate } else { $null }
                $newBillable = if ($PSBoundParameters.ContainsKey('Billable')) { $Billable } else { $entry.Billable }
                $newResource = if ($PSBoundParameters.ContainsKey('ResourceId')) { $ResourceId } else { $entry.ResourceId }

                $target = Resolve-LedgerTimeEntryTarget -Context (Get-LedgerTimeContext -JournalPath $JournalPath) `
                    -ResourceId $newResource -ProjectNumber $newProject -CustomerNumber $newCustomer -Billable $newBillable -Rate $newRate
                $entry.ResourceId = $target.ResourceId
                $entry.ProjectNumber = $target.ProjectNumber
                $entry.CustomerNumber = $target.CustomerNumber
                $entry.Billable = $target.Billable
                $entry.Rate = $target.Rate
            }
            $entry.InvoiceNumber = $null

            if ($PSCmdlet.ShouldProcess("Time entry $EntryId", 'Update time entry')) {
                Save-LedgerTimeEntries -JournalPath $JournalPath -Entries $entries -Months @($oldMonth, $entry.Date.ToString('yyyy-MM'))
                if ($PassThru) {
                    Get-LedgerTimeEntry -JournalPath $JournalPath -EntryId $EntryId
                }
            }
        }
        finally {
            Exit-LedgerJournalLock -Lock $journalLock
        }
    }
}