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

        $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
            }
        }
    }
}