Modules/AzureDevOpsDsc.Common/Api/Functions/Private/Api/Team/Set-DevOpsTeamSettings.ps1

Function Set-DevOpsTeamSettings
{
    <#
        .SYNOPSIS
            Applies the iteration and area-path configuration for an Azure DevOps team.
        .DESCRIPTION
            Resolves iteration and area paths to classification-node identifiers and applies them
            to the team via the 'teamsettings', 'teamsettings/iterations' and
            'teamsettings/teamfieldvalues' endpoints.
    #>

    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Mandatory)][string]$ApiUri,
        [Parameter(Mandatory)][string]$ProjectId,
        [Parameter(Mandatory)][string]$TeamId,
        [Parameter()][string]$BacklogIterationPath,
        [Parameter()][string]$DefaultIterationPath,
        [Parameter()][string[]]$IterationPaths,
        [Parameter()][string]$DefaultAreaPath,
        [Parameter()][string[]]$AreaPaths,
        [Parameter()][string[]]$WorkingDays,
        [Parameter()][ValidateSet('', 'asRequirements', 'asTasks', 'off')][string]$BugsBehavior,
        [Parameter()][string]$ApiVersion = '7.1'
    )

    $org  = $ApiUri.TrimEnd('/')
    $base = '{0}/{1}/{2}/_apis/work/teamsettings' -f $org, $ProjectId, $TeamId

    # Resolve a classification node (iteration|area) path to its node identifier.
    function Resolve-ClassificationNode
    {
        param([string]$Structure, [string]$Path)
        if ([string]::IsNullOrWhiteSpace($Path)) { return $null }
        # Strip a leading '<Project>\' prefix so the path is relative to the node root. A path with
        # no separator IS the project/root name alone - resolve it to the root node (no trailing
        # segment), not a non-existent child node named after the project.
        $relative = if ($Path -match '[\\/]') { ($Path -replace '^[^\\/]+[\\/]', '') -replace '\\', '/' } else { '' }
        $uri = if ($relative) {
            '{0}/{1}/_apis/wit/classificationnodes/{2}/{3}?api-version={4}' -f $org, $ProjectId, $Structure, $relative, $ApiVersion
        } else {
            '{0}/{1}/_apis/wit/classificationnodes/{2}?api-version={3}' -f $org, $ProjectId, $Structure, $ApiVersion
        }
        try   { return Invoke-AzDevOpsApiRestMethod -Uri $uri -Method Get }
        catch { Throw "[Set-DevOpsTeamSettings] Failed to resolve $Structure path '$Path': $_" }
    }

    try
    {
        # --- Iterations -----------------------------------------------------------------
        $settingsBody = @{}
        if ($PSBoundParameters.ContainsKey('BacklogIterationPath') -and $BacklogIterationPath)
        {
            $settingsBody.backlogIteration = (Resolve-ClassificationNode -Structure 'iterations' -Path $BacklogIterationPath).identifier
        }
        if ($PSBoundParameters.ContainsKey('DefaultIterationPath') -and $DefaultIterationPath)
        {
            $settingsBody.defaultIteration = (Resolve-ClassificationNode -Structure 'iterations' -Path $DefaultIterationPath).identifier
        }
        if ($PSBoundParameters.ContainsKey('WorkingDays'))
        {
            $settingsBody.workingDays = @($WorkingDays)
        }
        if ($PSBoundParameters.ContainsKey('BugsBehavior') -and $BugsBehavior)
        {
            $settingsBody.bugsBehavior = $BugsBehavior
        }
        if ($settingsBody.Count -gt 0 -and $PSCmdlet.ShouldProcess($TeamId, 'Update team iteration settings'))
        {
            Invoke-AzDevOpsApiRestMethod -Uri ('{0}?api-version={1}' -f $base, $ApiVersion) -Method Patch `
                -ContentType 'application/json' -Body ($settingsBody | ConvertTo-Json -Depth 5) | Out-Null
        }

        # --- Area paths (team field values) --------------------------------------------
        # Must run before iteration assignment below - Azure DevOps rejects adding an
        # iteration to a team's backlog ("TF400499: You have not set your team field")
        # until the team has an area path (team field) configured.
        if ($PSBoundParameters.ContainsKey('DefaultAreaPath') -or $PSBoundParameters.ContainsKey('AreaPaths'))
        {
            $values = @()
            foreach ($areaPath in @($AreaPaths)) { if ($areaPath) { $values += @{ value = $areaPath; includeChildren = $true } } }

            $defaultValue = $DefaultAreaPath
            if ([string]::IsNullOrWhiteSpace($defaultValue) -and $values.Count -gt 0) { $defaultValue = $values[0].value }
            if ($defaultValue -and -not ($values | Where-Object { $_.value -eq $defaultValue }))
            {
                $values += @{ value = $defaultValue; includeChildren = $true }
            }

            $fieldBody = @{ defaultValue = $defaultValue; values = $values }
            if ($PSCmdlet.ShouldProcess($TeamId, 'Update team area paths'))
            {
                Invoke-AzDevOpsApiRestMethod -Uri ('{0}/teamfieldvalues?api-version={1}' -f $base, $ApiVersion) -Method Patch `
                    -ContentType 'application/json' -Body ($fieldBody | ConvertTo-Json -Depth 5) | Out-Null
            }
        }

        # Assign the team's iteration backlog (the iterations the team participates in).
        if ($PSBoundParameters.ContainsKey('IterationPaths') -and $IterationPaths)
        {
            foreach ($iterationPath in $IterationPaths)
            {
                $node = Resolve-ClassificationNode -Structure 'iterations' -Path $iterationPath
                if ($node -and $PSCmdlet.ShouldProcess($iterationPath, 'Assign iteration to team'))
                {
                    Invoke-AzDevOpsApiRestMethod -Uri ('{0}/iterations?api-version={1}' -f $base, $ApiVersion) -Method Post `
                        -ContentType 'application/json' -Body (@{ id = $node.identifier } | ConvertTo-Json) | Out-Null
                }
            }
        }
    }
    catch
    {
        Throw "[Set-DevOpsTeamSettings] Failed to apply team settings for team '$TeamId': $_"
    }

    return Get-DevOpsTeamSettings -ApiUri $ApiUri -ProjectId $ProjectId -TeamId $TeamId -ApiVersion $ApiVersion
}