public/integrations/update-ProjectItemsStatusOnDueDate.ps1

<#
.SYNOPSIS
    Updates (stages) project item statuses based on a due date field.

.DESCRIPTION
    This cmdlet helps you maintain the status of project items based on a planning date field (e.g. 'DueDate', 'Target', etc.).
    The goal is set items to Action Required when due date is passed or reached, or set a Planned status if the due date is in the future.
    If items are closed (aka status "Done"), their due date is cleared.
    
    To achieve this objective here the loggig implemented:
    For each project item that has the specified due date field:
      - If the due date is today or in the past:
          * Change its status to the value in -StatusAction when its current status equals -StatusPlanned.
          * If -AnyStatus is specified, change any (non-done) item with a past/today due date to -StatusAction (except items filtered out or cleared as done).
      - If the due date is in the future and the current status equals -StatusAction, change it back to -StatusPlanned.
      - If -IncludeDoneItems is set, items whose status is exactly 'Done' have the due date field cleared (status is not modified).
      - If -StatusDone is provided, items whose status equals that value have the due date field cleared.
    It is an error to specify both -AnyStatus and -StatusDone.
    All edits are staged locally (not immediately pushed). Use Show-ProjectItemStaged, Save-ProjectItemStaged, or Reset-ProjectItemStaged as needed.

.PARAMETER Owner
    Owner of the project. Optional if a default context is configured.

.PARAMETER ProjectNumber
    Numeric project number. Optional if a default context is configured.

.PARAMETER StatusFieldName
    Name of the project field that holds the status value (e.g. 'Status').

.PARAMETER DateFieldName
    Name of the project field that stores the due / target date (must contain date values).

.PARAMETER StatusAction
    Status value that represents an actionable / ready state (e.g. 'ActionRequired').

.PARAMETER StatusPlanned
    Status value that represents a planned / future / scheduled state (e.g. 'Planned').

.PARAMETER StatusDone
    Optional alternate done-like status whose items should have their due date cleared (e.g. 'Cancelled', 'Won't Do').
    Cannot be used together with -AnyStatus.

.PARAMETER AnyStatus
    If specified, any item (except system Done or -StatusDone items cleared earlier) with a due date of today/past is moved to -StatusAction.
    Mutually exclusive with -StatusDone.

.PARAMETER IncludeDoneItems
    Include items whose status is exactly 'Done'. Their due date is cleared (status unchanged).

.PARAMETER Force
    Force refresh/sync of the project items content before processing. If not set will used project cached information if available.

.OUTPUTS
    None. Writes no output. All modifications are staged.

.EXAMPLE
    Update-ProjectItemsStatusOnDueDate -Owner octodemo -ProjectNumber 625 `
      -StatusFieldName Status -DateFieldName DueDate `
      -StatusAction ActionRequired -StatusPlanned Planned
    Moves overdue Planned items to ActionRequired; moves future ActionRequired items back to Planned.

.EXAMPLE
    Update-ProjectItemsStatusOnDueDate -StatusFieldName Status -DateFieldName Target `
      -StatusAction "In Progress" -StatusPlanned Planned -AnyStatus
    Forces every overdue item (except 'Done') into In Progress regardless of prior status.

.EXAMPLE
    Update-ProjectItemsStatusOnDueDate -StatusFieldName Status -DateFieldName Due `
      -StatusAction ActionRequired -StatusPlanned Planned -IncludeDoneItems
    Also clears Due on items already marked Done.

.EXAMPLE
    Update-ProjectItemsStatusOnDueDate -StatusFieldName Status -DateFieldName Due `
      -StatusAction ActionRequired -StatusPlanned Planned -StatusDone Cancelled
    Clears Due on items whose status is Cancelled (treating them as done-like).

.NOTES
    Throws if both -AnyStatus and -StatusDone are supplied.
    Avoid conflict when item is in the past with the Specified Done Status. reopening closed items when Status Done and has Due field in the past.
    In this cases we could set to Action status or clear DueDate.
#>

function Update-ProjectItemsStatusOnDueDate{
    [CmdletBinding()]
    param(
        [Parameter()][string]$Owner,
        [Parameter()][int]$ProjectNumber,
        [Parameter()][string]$StatusFieldName = "Status",
        [Parameter(Mandatory)][string]$DateFieldName,
        [Parameter(Mandatory)][string]$StatusAction,
        [Parameter(Mandatory)][string]$StatusPlanned,
        [Parameter()][string]$StatusDone,
        [Parameter()][switch]$AnyStatus,
        [Parameter()][switch]$IncludeDoneItems,
        [Parameter()][switch]$Force
    )

    "Updating project items status with due date for project $owner/$ProjectNumber" | Write-MyHost

    ($Owner,$ProjectNumber) = Get-OwnerAndProjectNumber -Owner $Owner -ProjectNumber $ProjectNumber
    if([string]::IsNullOrWhiteSpace($owner) -or [string]::IsNullOrWhiteSpace($ProjectNumber)){ "Owner and ProjectNumber are required" | Write-MyError; return $null}

    if((-not $SkipProjectSync) -AND (Test-ProjectItemStaged -Owner $Owner -ProjectNumber $ProjectNumber)){
        "Project has staged items, please Sync-ProjectItemStaged or Reset-ProjectItemStaged and try again" | Write-Error
        return
    }

    # Sync project if needed
    $null = Get-Project -Owner $Owner -ProjectNumber $ProjectNumber -Force:$Force

    $params = @{
        Owner = $Owner
        ProjectNumber = $ProjectNumber
        DateFieldName = $DateFieldName
        StatusFieldName = $StatusFieldName
        StatusAction = $StatusAction
        StatusPlanned = $StatusPlanned
        StatusDone = $StatusDone
        AnyStatus = $AnyStatus
        IncludeDoneItems = $IncludeDoneItems
    }

    # Call the injection type function
    $ret = Invoke-ProjectInjectionOnDueDate @params

    return $ret

} Export-ModuleMember -Function Update-ProjectItemsStatusOnDueDate

function Invoke-ProjectInjectionOnDueDate {
    [CmdletBinding(SupportsShouldProcess)]
    param (
        [Parameter()][string]$Owner,
        [Parameter()][int]$ProjectNumber,
        [Parameter(Mandatory)][string]$StatusFieldName,
        [Parameter(Mandatory)][string]$DateFieldName,
        [Parameter(Mandatory)][string]$StatusAction,
        [Parameter(Mandatory)][string]$StatusPlanned,
        [Parameter()][string]$StatusDone,
        [Parameter()][switch]$AnyStatus,
        [Parameter()][switch]$IncludeDoneItems
    )

    ($Owner,$ProjectNumber) = Get-OwnerAndProjectNumber -Owner $Owner -ProjectNumber $ProjectNumber
    if([string]::IsNullOrWhiteSpace($owner) -or [string]::IsNullOrWhiteSpace($ProjectNumber)){ "Owner and ProjectNumber are required" | Write-MyError; return $null}

    $items = Get-ProjectItemList -Owner $Owner -ProjectNumber $ProjectNumber -ExcludeDone:$(-Not $IncludeDoneItems)

    foreach($item in $items.Values){

        function EditItem($FieldName,$Value){
            $params = @{
                Owner = $Owner
                ProjectNumber = $ProjectNumber
                ItemId = $item.id
                FieldName = $FieldName
                Value = $Value
            }

            if ($PSCmdlet.ShouldProcess($item.Title, "edit to $FieldName [$Value]")) {
                Edit-ProjectItem @params
            }
        }

        # Skip if the item does not have the due date field
        if(-not $item.$DateFieldName){
            continue
        }

        # Skip if the item is not overdue
        $today = Get-DateToday
        $actualDate = $item.$DateFieldName
        $actualStatus = $item.Status

        # IncludeDoneItems --> Clear date on items that are system done
        if (($IncludeDoneItems) -and ($actualStatus -eq "Done")) {
            # Clear DueDate to done items
            EditItem $DateFieldName ""
            continue
        }

        $isPastOrToday = $actualDate -le $today
        $isFuture = ! $isPastOrToday
        $isActionRequired = $actualStatus -eq $StatusAction
        $isSetOtherDone = ! [string]::IsNullOrWhiteSpace($StatusDone)
        $isOtherDone = ($actualStatus -eq $StatusDone)

        # Clear date on items where status is $StatusDone
        if ($isSetOtherDone -and $isOtherDone ) {
            # Clear DueDate to done items
            EditItem $DateFieldName ""
            continue
        }

        # Move to StatusAction if today or due, and status is Planned or AnyStatus is set
        if ( $isPastOrToday -and (($actualStatus -eq $StatusPlanned) -or ($AnyStatus))){
            EditItem $StatusFieldName $StatusAction
            continue
        }

        # Move to Planned if ActionRequred but in the future
        if( $isFuture -and $isActionRequired){
            EditItem $StatusFieldName $StatusPlanned
            continue
        }
    }
} # Do not export this function to avoid conflicts with Update-ProjectItemsWithIntegration