private/projectDatabase/project_database_fields.ps1


function Get-Field{
    [CmdletBinding()]
    [OutputType([object[]])]
    param(
        [Parameter(Position = 0)][object[]]$Database,
        [Parameter(Position = 1)][string]$FieldName
    )

    $field = $Database.fields.Values | Where-Object { $_.name -eq $FieldName }

    return $field
}

function Test-FieldValue{
    [CmdletBinding()]
    [OutputType([bool])]
    param(
        [Parameter(Position = 1)][object]$Field,
        [Parameter(Position = 2)][string]$Value
    )
    $dataType = $Field.dataType

    if([string]::IsNullOrEmpty($Value)){
        # If the value is null or empty, we assume it is a valid value as a no value
        return $true
    }

    switch ($dataType) {
        "NUMBER"         { $ret = $Value | Test-NumberFormat               ;Break }
        "DATE"           { $ret = $Value | Test-DateFormat                 ;Break }
        "SINGLE_SELECT"  { $ret = $Value | Test-SingleSelect -Field $Field ;Break }

        # default to true as any string or null is valid
        default          { $ret = $true }
    }

    if(-not $ret){
        "not valid value [$Value] for field [$($Field.name)] with type [$($Field.dataType)]" | Write-Verbose
    }

    return $ret
}

function ConvertTo-FieldValue{
    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter(Position = 0)][object]$Field,
        [Parameter(Position = 1)][string]$Value
    )

    $dataType = $Field.dataType

    switch ($dataType) {
        "NUMBER"         { $ret = $value | ConvertTo-Number                 ;Break}
        "SINGLE_SELECT"  { $ret = $value | ConvertTo-SingleSelect $Field    ;Break}

        # default no transformation needed
        default          { $ret = $value }
    }

    return $ret
}

function ConvertFrom-FieldValue{
    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter(Position = 0)][object]$Field,
        [Parameter(Position = 1)][string]$Value
    )

    $dataType = $Field.dataType

    switch ($dataType) {
        "SINGLE_SELECT"  { $ret = $value| ConvertFrom-SingleSelect -Field $Field ;Break}

        # Default no transformation needed
        default          { $ret = $value }
    }

    return $ret
}

<#.SYNOPSIS
    Convert from OptionId to OptionName
#>

function ConvertFrom-SingleSelect{
    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter(Position = 0)][object]$Field,
        [Parameter(ValueFromPipeline)][string]$Value
    )
    process{

        if([string]::IsNullOrEmpty($Value)){
            return $null
        }

        $ret = $Field.options.Keys | Where-Object {$Field.options.$_ -eq $value}

        if($null -eq $ret){
            throw "Invalid SingleSelect value [$Value] for field [$($Field.name)] with type [$($Field.dataType)]"
        }

        return $ret
    }
}

<#.SYNOPSIS
    Convert from OptionName to OptionId
#>

function ConvertTo-SingleSelect{
    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter(Position = 0)][object]$Field,
        [Parameter(ValueFromPipeline)][string]$Value
    )
    process{
        $ret = $Field.options.$Value
        return $ret
    }
}

function Test-SingleSelect{
    [CmdletBinding()]
    [OutputType([bool])]
    param(
        [Parameter(ValueFromPipeline,Position = 0)][string]$Value,
        [Parameter(Position = 1)][object]$Field
    )

    process{

        try{
            $result = ConvertTo-SingleSelect -Field $Field -Value $Value

            if($null -eq $result){
                return $false
            }

            return $true
        }
        catch {
            return $false
        }
    }
}

# function Test-DateFormat what will test strings with the date format YYYY-MM-DD
function Test-DateFormat{
    [CmdletBinding()]
    [OutputType([bool])]
    param(
        [Parameter(ValueFromPipeline,Position = 0)][string]$Date
    )

    process{

        try {
            $null = [datetime]::ParseExact($Date, 'yyyy-MM-dd', $null)
            
            return $true
        }
        catch {
            return $false
        }
    }
}

function Test-SingleSelectFormat{
    [CmdletBinding()]
    [OutputType([bool])]
    param(
        [Parameter(ValueFromPipeline,Position = 0)][string]$Value,
        [Parameter(Position = 1)][object]$Field
    )

    process{
        }

}

function Test-NumberFormat{
    [CmdletBinding()]
    [OutputType([bool])]
    param(
        [Parameter(ValueFromPipeline,Position = 0)][string]$Number
    )

    process{

        if([string]::IsNullOrEmpty($Number)){
            # If the number is null or empty, we assume it is a valid value
            return $true
        }

        return $null -ne $(Get-NumberFormatCulture $Number)
    }
}

function ConvertTo-Number {
    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter(ValueFromPipeline)][string]$Value
    )
    process {
        $regex = [regex]"^[^\d-]*(-?(?:\d|(?<=\d)\.(?=\d{3}))+(?:,\d+)?|-?(?:\d|(?<=\d),(?=\d{3}))+(?:\.\d+)?)[^\d]*$"

        # Get the numeric part from the string
        $match = $regex.Match($Value)
        $numberPart = $match.Groups[1].Value

        # Try to guess which character is used for decimals and which is used for thousands
        if ($numberPart.LastIndexOf(',') -gt $numberPart.LastIndexOf('.')) {
            $decimalChar = ','
            $thousandsChar = '.'
        }
        else {
            $decimalChar = '.'
            $thousandsChar = ','
        }

        # Remove thousands separators as they are not needed for parsing
        $numberPart = $numberPart.Replace($thousandsChar, '')

        # Replace decimal separator with the one from InvariantCulture
        # This makes sure the decimal parses successfully using InvariantCulture
        $numberPart = $numberPart.Replace($decimalChar, 
            [System.Globalization.CultureInfo]::InvariantCulture.NumberFormat.CurrencyDecimalSeparator)
            
        return $numberPart
    }
}

function Get-NumberFormatCulture{
    [CmdletBinding()]
    [OutputType([bool])]
    param(
        [Parameter(ValueFromPipeline,Position = 0)][string]$Number
    )

    process{

        # Try to parse with current culture first
        if ([decimal]::TryParse($Number, [System.Globalization.NumberStyles]::Any, [System.Globalization.CultureInfo]::CurrentCulture, [ref]$null)) {
            return [System.Globalization.CultureInfo]::CurrentCulture
        }
        # If that fails, try with invariant culture
        elseif ([decimal]::TryParse($Number, [System.Globalization.NumberStyles]::Any, [System.Globalization.CultureInfo]::InvariantCulture, [ref]$null)) {
            return [System.Globalization.CultureInfo]::InvariantCulture
        }
        elseif ([decimal]::TryParse($Number, [System.Globalization.NumberStyles]::Any, [System.Globalization.CultureInfo]::GetCultureInfo("es-ES"), [ref]$null)) {
            return [System.Globalization.CultureInfo]::GetCultureInfo("es-ES")
        }
        else {
            return $null
        }
    }
}