Varibill.SourceData.psm1

enum SourceDataType 
{
    Unrated 
    Rated
}

class SourceDataBase
{
    hidden [string] $apiUrl
    hidden [SourceDataType] $sourceDataType
    hidden [guid] $sourceCollectorKey
    hidden [xml] $sourceXml
    hidden [xml] $resultXml
    hidden [string] $path
    
    SourceDataBase()
    {
    }

    SourceDataBase([string]$apiUrl, [string]$sourceCollectorName, [guid]$sourceCollectorKey) 
    {   
        $this.apiUrl = $apiUrl
        $this.sourceCollectorKey = $sourceCollectorKey

        $fileOrPath = Join-Path -Path $global:HOME -ChildPath 'varibill'
        $fileOrPath = Join-Path -Path $fileOrPath -ChildPath 'collectors'
        $fileOrPath = Join-Path -Path $fileOrPath -ChildPath $sourceCollectorKey
        $this.path = $fileOrPath

        Write-Verbose "The root path for the collector is $fileOrPath"
        Write-Verbose "The collector API URL is $apiUrl"
        Write-Verbose "The collector name is $sourceCollectorName and the key is $sourceCollectorKey"
    }

    SaveSourceData()
    {
        $now = Get-Date -Format "yyyyMMdd_HHmmss_fff" 
        $now += '.xml'
        $dataPath = $this.path
        switch ($this.sourceDataType)
        {
            Unrated
            {
                $dataPath = Join-Path -Path $dataPath -ChildPath 'unrated'
            }
            Rated
            {
                $dataPath = Join-Path -Path $dataPath -ChildPath 'unrated'
            }
        }

        New-Item -ItemType Directory -Force -Path $dataPath

        $file = Join-Path -Path $dataPath -ChildPath $now

        $this.sourceXml.Save($file)

        Write-Host "Data file saved to $file"
    }

    SaveSourceData([string] $file)
    {
        $this.sourceXml.Save($file)
    }

    [xml] GetSourceData()
    {
        return $this.sourceXml
    }

    SaveResultXml()
    {
        if ($this.resultXml -ne $null)
        {
            $now = Get-Date -Format "yyyyMMdd_HHmmss_fff"
            $now += '.xml'

            $logPath = Join-Path -Path $this.path -ChildPath 'log'

            New-Item -ItemType Directory -Force -Path $logPath

            $file = Join-Path -Path $logPath -ChildPath $now

            $this.resultXml.Save($file)

            Write-Host "Log file saved to $file"
        }
    }

    SaveResultXml([string] $fileOrPath)
    {
        if ($this.resultXml -ne $null)
        {
            $this.resultXml.Save($fileOrPath)
        }
    }

    [xml] GetResultXml()
    {
        return $this.resultXml
    }
}

class SourceData : SourceDataBase
{
    hidden $recordsNode

    SourceData([string]$apiUrl, [string]$sourceCollectorName, [guid]$sourceCollectorKey)  : base([string]$apiUrl, [string]$sourceCollectorName, [guid]$sourceCollectorKey)
    {
        $sourceDataType = "Unrated";

        $this.sourceXml = New-Object System.Xml.XmlDocument;

        $prolog = $this.sourceXml.CreateXmlDeclaration("1.0", "ISO-8859-15", $null);

        $namespaceSoapenv = "http://schemas.xmlsoap.org/soap/envelope/";
        $namespaceTem = "http://tempuri.org/";
        $namespaceVar = "http://schemas.datacontract.org/2004/07/Varibill.WebService";

        $this.sourceXml.AppendChild($prolog) | Out-Null;

        #Build the Envelope node
        $envelopeNode = $this.sourceXml.CreateNode([System.Xml.XmlNodeType]::Element, "soapenv:Envelope", $namespaceSoapenv);
        $envelopeNode.SetAttribute("xmlns:var", $namespaceVar)
        $envelopeNode.SetAttribute("xmlns:tem", $namespaceTem)
        $this.sourceXml.AppendChild($envelopeNode) | Out-Null;

        #Build the Body node
        $bodyNode = $this.sourceXml.CreateNode([System.Xml.XmlNodeType]::Element, "soapenv:Body", $namespaceSoapenv);
        $envelopeNode.AppendChild($bodyNode) | Out-Null;

        #Build the ImportSourceData node
        $importSourceDataNode = $this.sourceXml.CreateNode([System.Xml.XmlNodeType]::Element, "tem:ImportSourceData", $namespaceTem);
        $bodyNode.AppendChild($importSourceDataNode) | Out-Null;

        # Build the Source node
        $sourceNode = $this.sourceXml.CreateNode([System.Xml.XmlNodeType]::Element, "tem:source", $namespaceTem);

            $element = $this.sourceXml.CreateElement("var:Key", $namespaceVar)
            $element.InnerText = $sourceCollectorKey;
            $sourceNode.AppendChild($element) | Out-Null;

            $element = $this.sourceXml.CreateElement("var:Name", $namespaceVar)
            $element.InnerText = $sourceCollectorName;
            $sourceNode.AppendChild($element) | Out-Null;

        $importSourceDataNode.AppendChild($sourceNode) | Out-Null;
        # End the Source Node

        #Build the Records node
        $this.recordsNode = $this.sourceXml.CreateNode([System.Xml.XmlNodeType]::Element, "tem:records", $namespaceTem);
        $importSourceDataNode.AppendChild($this.recordsNode) | Out-Null;
    }

    AddSourceDataRecord([string] $clientIdentifier, [datetime] $lastSeenDateTime, [string] $productCode, [decimal] $quantity, [string] $recordGUID, [string] $recordIdentifier)
    {
        # Build the Record node
        $namespaceVar = "http://schemas.datacontract.org/2004/07/Varibill.WebService";

        $recordNode = $this.sourceXml.CreateElement("var:Record", $namespaceVar);

        $element = $this.sourceXml.CreateElement("var:ClientIdentifier", $namespaceVar)
        $element.InnerText = $clientIdentifier
        $recordNode.AppendChild($element) | Out-Null;

        $element = $this.sourceXml.CreateElement("var:LastSeenDateTime", $namespaceVar)
        $element.InnerText = $lastSeenDateTime.ToString("o")
        $recordNode.AppendChild($element) | Out-Null;

        $element = $this.sourceXml.CreateElement("var:ProductCode", $namespaceVar)
        $element.InnerText = $productCode
        $recordNode.AppendChild($element) | Out-Null;

        $element = $this.sourceXml.CreateElement("var:Quantity", $namespaceVar)
        $element.InnerText = $quantity
        $recordNode.AppendChild($element) | Out-Null;

        $element = $this.sourceXml.CreateElement("var:RecordGUID", $namespaceVar)
        $element.InnerText = $recordGUID
        $recordNode.AppendChild($element) | Out-Null;

        $element = $this.sourceXml.CreateElement("var:RecordIdentifier", $namespaceVar)
        $element.InnerText = $recordIdentifier
        $recordNode.AppendChild($element) | Out-Null;

        $this.recordsNode.AppendChild($recordNode) | Out-Null;
        # End Record Node
    }

    [bool] PostSourceData()
    {
        $headers = @{"SOAPAction" = "http://tempuri.org/ISourceData/ImportSourceData"}

        $postXml = $this.sourceXml
        if($postXml.FirstChild.NodeType -eq 'XmlDeclaration')
        {
            $postXml.FirstChild.Encoding = $null
        }

        if ($global:PSVersionTable.PSVersion.Major -ge 7)
        {
            $this.resultXml = Invoke-WebRequest $this.apiUrl -Method post -ContentType 'text/xml' -Body $postXml.OuterXML -Headers $headers -RetryIntervalSec 5 -MaximumRetryCount 5
        }
        else
        {
            $this.resultXml = Invoke-WebRequest $this.apiUrl -Method post -ContentType 'text/xml' -Body $postXml.OuterXML -Headers $headers
        }

        $this.resultXml.GetElementsByTagName("a:ExitCode").innerXML

        return ($this.resultXml.GetElementsByTagName("a:ExitCode").innerXML -eq 0)
    }
}

class RatedSourceData : SourceDataBase
{
    hidden $ratedRecordsNode

    RatedSourceData([string]$apiUrl, [string]$sourceCollectorName, [guid]$sourceCollectorKey) : base([string]$apiUrl, [string]$sourceCollectorName, [guid]$sourceCollectorKey)
    {
        $sourceDataType = "Rated";

        $this.sourceXml = New-Object System.Xml.XmlDocument;

        $prolog = $this.sourceXml.CreateXmlDeclaration("1.0", "ISO-8859-15", $null);

        $namespaceSoapenv = "http://schemas.xmlsoap.org/soap/envelope/";
        $namespaceTem = "http://tempuri.org/";
        $namespaceVar = "http://schemas.datacontract.org/2004/07/Varibill.WebService";

        $this.sourceXml.AppendChild($prolog) | Out-Null;

        #Build the Envelope node
        $envelopeNode = $this.sourceXml.CreateNode([System.Xml.XmlNodeType]::Element, "soapenv:Envelope", $namespaceSoapenv);
        $envelopeNode.SetAttribute("xmlns:var", $namespaceVar)
        $envelopeNode.SetAttribute("xmlns:tem", $namespaceTem)
        $this.sourceXml.AppendChild($envelopeNode) | Out-Null;

        #Build the Body node
        $bodyNode = $this.sourceXml.CreateNode([System.Xml.XmlNodeType]::Element, "soapenv:Body", $namespaceSoapenv);
        $envelopeNode.AppendChild($bodyNode) | Out-Null;

        #Build the ImportSourceData node
        $importSourceDataNode = $this.sourceXml.CreateNode([System.Xml.XmlNodeType]::Element, "tem:ImportRatedSourceData", $namespaceTem);
        $bodyNode.AppendChild($importSourceDataNode) | Out-Null;

        # Build the Source node
        $sourceNode = $this.sourceXml.CreateNode([System.Xml.XmlNodeType]::Element, "tem:source", $namespaceTem);

            $element = $this.sourceXml.CreateElement("var:Key", $namespaceVar)
            $element.InnerText = $sourceCollectorKey;
            $sourceNode.AppendChild($element) | Out-Null;

            $element = $this.sourceXml.CreateElement("var:Name", $namespaceVar)
            $element.InnerText = $sourceCollectorName;
            $sourceNode.AppendChild($element) | Out-Null;

        $importSourceDataNode.AppendChild($sourceNode) | Out-Null;
        # End the Source Node

        #Build the Records node
        $this.ratedRecordsNode = $this.sourceXml.CreateNode([System.Xml.XmlNodeType]::Element, "tem:ratedRecords", $namespaceTem);
        $importSourceDataNode.AppendChild($this.ratedRecordsNode) | Out-Null;
    }

    AddSourceDataRecord([string] $clientIdentifier, [string] $productCode, [string] $recordGUID, [string] $recordIdentifier, [datetime] $lastSeenDateTime, [decimal] $quantity, [decimal] $total)
    {
        # Build the Record node
        $namespaceVar = "http://schemas.datacontract.org/2004/07/Varibill.WebService";

        $ratedRecordNode = $this.sourceXml.CreateElement("var:RatedRecord", $namespaceVar);

        $element = $this.sourceXml.CreateElement("var:ClientIdentifier", $namespaceVar)
        $element.InnerText = $clientIdentifier
        $ratedRecordNode.AppendChild($element) | Out-Null;

        $element = $this.sourceXml.CreateElement("var:LastSeenDateTime", $namespaceVar)
        $element.InnerText = $lastSeenDateTime.ToString("o")
        $ratedRecordNode.AppendChild($element) | Out-Null;

        $element = $this.sourceXml.CreateElement("var:ProductCode", $namespaceVar)
        $element.InnerText = $productCode
        $ratedRecordNode.AppendChild($element) | Out-Null;

         $element = $this.sourceXml.CreateElement("var:Quantity", $namespaceVar)
        $element.InnerText = $quantity
        $ratedRecordNode.AppendChild($element) | Out-Null;

        $element = $this.sourceXml.CreateElement("var:RecordGUID", $namespaceVar)
        $element.InnerText = $recordGUID
        $ratedRecordNode.AppendChild($element) | Out-Null;

        $element = $this.sourceXml.CreateElement("var:RecordIdentifier", $namespaceVar)
        $element.InnerText = $recordIdentifier
        $ratedRecordNode.AppendChild($element) | Out-Null;

        $element = $this.sourceXml.CreateElement("var:Total", $namespaceVar)
        $element.InnerText = $total
        $ratedRecordNode.AppendChild($element) | Out-Null;

        $this.ratedRecordsNode.AppendChild($ratedRecordNode) | Out-Null;
        # End Record Node
    }

    [bool] PostSourceData()
    {
        $headers = @{"SOAPAction" = "http://tempuri.org/IRatedSourceData/ImportRatedSourceData"}

        $postXml = $this.sourceXml
        if($postXml.FirstChild.NodeType -eq 'XmlDeclaration')
        {
            $postXml.FirstChild.Encoding = $null
        }

        if ($global:PSVersionTable.PSVersion.Major -ge 7)
        {
            $this.resultXml = Invoke-WebRequest $this.apiUrl -Method post -ContentType 'text/xml' -Body $postXml.OuterXML -Headers $headers -RetryIntervalSec 5 -MaximumRetryCount 5
        }
        else
        {
            $this.resultXml = Invoke-WebRequest $this.apiUrl -Method post -ContentType 'text/xml' -Body $postXml.OuterXML -Headers $headers
        }

        $this.resultXml.GetElementsByTagName("a:ExitCode").innerXML

        return ($this.resultXml.GetElementsByTagName("a:ExitCode").innerXML -eq 0)
    }
}

<#
.SYNOPSIS
The Varibill.SourceCollector module provides the framework so that base data can be easily post to the Varibill web service for consumption.
 
.DESCRIPTION
Varibill collects data from various source systems for billing purposes. Some of this data is collected via Powershell, and needs to be posted to the
Varibill web service for billing purposes.
 
This module absctracts the creation and posting of the XML data so that the users can focus on the business requirements.
 
More information can be found at https://docs.varibill.com
 
.PARAMETER ApiUrl
The URL to the tenant specific API. The URL would follow the pattern of https://api-<COUNTRY>-<LOCATION>_<ID>.varibill.com/SourceData.svc
 
.PARAMETER SourceCollectorName
The name of the Source Collector as configured in Varibill.
 
.PARAMETER sourceCollectorKey
The key of the Source Collector as configured in Varibill.
 
.EXAMPLE
$sourceData = New-SourceData -ApiUrl 'https://api-<COUNTRY>-<LOCATION>_<ID>.varibill.com/SourceData.svc' -SourceCollectorName 'Source Collector Name' -sourceCollectorKey '00000000-0000-0000-0000-000000000000'
$sourceData.AddSourceDataRecord('Client Identifier', '2020-01-01 00:00:00', 'Product Code', 1.234, 'Unique Identifier', 'Record Identifier')
$sourceData.PostSourceData()
 
$ratedSourceData = New-RatedSourceData -ApiUrl 'https://api-<COUNTRY>-<LOCATION>_<ID>.varibill.com/SourceData.svc' -SourceCollectorName 'Source Collector Name' -sourceCollectorKey '00000000-0000-0000-0000-000000000000'
$ratedSourceData.AddSourceDataRecord('Client Identifier', 'Product Code', 'Unique Identifier', 'Record Identifier', '2020-01-01 00:00:00', 1.234, 9.876)
$ratedSourceData.PostSourceData()
 
.NOTES
General notes
#>

Function New-SourceData {
    [CmdletBinding(SupportsShouldProcess)]
    Param(
        [string] $ApiUrl,
        [string] $SourceCollectorName,
        [guid] $SourceCollectorKey
    )

    if ($PSCmdlet.ShouldProcess($SourceCollectorName))
    {
        [SourceData]::New($ApiUrl, $SourceCollectorName, $SourceCollectorKey)
    }
}

Function New-RatedSourceData {
    [CmdletBinding(SupportsShouldProcess)]
    Param(
        [string] $ApiUrl,
        [string] $SourceCollectorName,
        [guid] $SourceCollectorKey
    )

    if ($PSCmdlet.ShouldProcess($SourceCollectorName))
    {
        [RatedSourceData]::New($ApiUrl, $SourceCollectorName, $SourceCollectorKey)
    }
}

Export-ModuleMember -Function New-SourceData
Export-ModuleMember -Function New-RatedSourceData